commit ae3124d926bbeb72e6bd7d702848b3449d520c56 Author: balkian Date: Fri Mar 7 09:42:31 2025 +0000 Deploying to gh-pages from @ balkian/balkian.github.com@f5353dd1939b387536c202b89d6a3340d26b6ab7 🚀 diff --git a/404.html b/404.html new file mode 100644 index 0000000..3e9661b --- /dev/null +++ b/404.html @@ -0,0 +1,17 @@ +404 Page not found +

Not Found

This page does not exist

+ +

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..acfba47 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +balkian.com \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..201ec14 --- /dev/null +++ b/about/index.html @@ -0,0 +1,30 @@ +About +

About

Hello there, stranger! 👋

About me

My name is Fernando, and I like learning and solving hard problems. +Especially when it comes to computers, engineering and languages.

I currently work at the Technical University of Madrid (UPM) as an assistant professor in the school of Telecommunications Engineering. +You can check out my previous projects, and my publications. +Feel free to get in touch through the comment section, an e-mail (my first initial @sanchezrada.es) or any other platform. +I am always happy to help and collaborate.

About this blog

I use this blog for future reference, to write down some of the lessons I learn so. +I also see it as an exercise in reflection and sorting out my ideas. +Although I mostly do this for myself, to keep some lasting notes for the future, I also do it in hopes it might help someone like me in the future.

Each post is an independent note. +To keep some structure I will try to stick to general categories (e.g., programming, project management, linux), and add meaningful tags to help you and me find this information in the future. +You may also use the search bar if you are looking for something specific and wondering if I’ve covered it. +For short thematically connected snippets and tips, I keep a dedicated section with Cheatsheets.

+Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 0000000..5390fdd --- /dev/null +++ b/archives/index.html @@ -0,0 +1,15 @@ +Archives +

Categories

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 0000000..eeb0a01 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,33 @@ +Categories +

Section

3 pages

Categories

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/categories/index.xml b/categories/index.xml new file mode 100644 index 0000000..ea0f050 --- /dev/null +++ b/categories/index.xml @@ -0,0 +1 @@ +Categories on J. Fernando Sánchezhttps://balkian.com/categories/Recent content in Categories on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 05 Mar 2025 09:25:54 +0100Reflectionshttps://balkian.com/categories/reflections/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/categories/reflections/Programminghttps://balkian.com/categories/programming/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/categories/programming/Linuxhttps://balkian.com/categories/linux/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/categories/linux/ \ No newline at end of file diff --git a/categories/linux/index.html b/categories/linux/index.html new file mode 100644 index 0000000..c6ab6b7 --- /dev/null +++ b/categories/linux/index.html @@ -0,0 +1,33 @@ +Category: Linux - J. Fernando Sánchez +

Categories

3 pages

Linux

Posts related to installing, maintaining and running GNU/Linux

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/categories/linux/index.xml b/categories/linux/index.xml new file mode 100644 index 0000000..071ee04 --- /dev/null +++ b/categories/linux/index.xml @@ -0,0 +1,249 @@ +Linux on J. Fernando Sánchezhttps://balkian.com/categories/linux/Recent content in Linux on J. Fernando SánchezHugo -- gohugo.ioen-usSat, 01 Jun 2019 00:00:01 +0000Linux on the Microsoft Surface Gohttps://balkian.com/p/linux-on-the-microsoft-surface-go/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/p/linux-on-the-microsoft-surface-go/<p>Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. +These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.</p> +<h2 id="installing-the-kernel">Installing the kernel +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --depth <span class="m">1</span> https://github.com/jakeday/linux-surface.git ~/linux-surface +</span></span><span class="line"><span class="cl">cp -a ~/linux-surface /media/&lt;your usb&gt; +</span></span></code></pre></td></tr></table> +</div> +</div><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -a /media/&lt;your usb&gt;/linux-surface ~/ +</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/linux-surface/ +</span></span><span class="line"><span class="cl">sudo sh setup.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="booting-ubuntu-first">Booting ubuntu first +</h2><p>Switch out of Windows S mode.</p> +<p>Boot into the &ldquo;Command Prompt&rdquo;.</p> +<p>From Windows go to &ldquo;change advanced startup options&rdquo; and select &ldquo;restart now&rdquo;.</p> +<p>When it reboots, choose the &ldquo;Troubleshoot&rdquo; option, then choose the &ldquo;Advanced options&rdquo; option, and finally choose the &ldquo;Command Prompt&rdquo; option.</p> +<p>After the device reboots, login to the command prompt and then you should see a terminal with X:\windows\system32&gt;</p> +<p>At the prompt, check your UEFI entries:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /enum firmware +</span></span></code></pre></td></tr></table> +</div> +</div><p>Copy UEFI entry of &ldquo;Windows Boot Manager&rdquo; to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d &ldquo;Ubuntu&rdquo;</p> +<p>Copy the printed GUID number including the braces {} using Ctrl+C</p> +<p>Set file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \EFI\ubuntu\grubx64.efi</p> +<p>Set Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /set <span class="o">{</span>fwbootmgr<span class="o">}</span> displayorder <span class="o">{</span>guid<span class="o">}</span> /addfirst +</span></span></code></pre></td></tr></table> +</div> +</div><p>Check your UEFI entries again: bcdedit /enum firmware You should see something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">Firmware Boot Manager +</span></span><span class="line"><span class="cl">--------------------- +</span></span><span class="line"><span class="cl">identifier <span class="o">{</span>fwbootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl">displayorder <span class="o">{</span>3510232e-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>bootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>2148799b-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a67-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a68-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl">timeout <span class="m">0</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.</p>Fixing HDMI flickeringhttps://balkian.com/p/fixing-hdmi-flickering/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/fixing-hdmi-flickering/<img src="https://balkian.com/img/rpi.png" alt="Featured image of post Fixing HDMI flickering" /><p>Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_drive</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_group</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_mode</span><span class="o">=</span><span class="mi">42</span> +</span></span><span class="line"><span class="cl"> <span class="n">disable_overscan</span><span class="o">=</span><span class="mi">1</span> +</span></span><span class="line"><span class="cl"> <span class="n">config_hdmi_boost</span><span class="o">=</span><span class="mi">7</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Linux Cheatsheethttps://balkian.com/cheatsheet/linux/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/linux/<img src="https://balkian.com/img/linux.png" alt="Featured image of post Linux Cheatsheet" /><h2 id="black-screen-and-lightdm-doesnt-unlock">Black screen and LightDM doesn&rsquo;t unlock +</h2><p>Add this to your /etc/lightdm/lightdm.conf file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[LightDM]</span> +</span></span><span class="line"><span class="cl"><span class="na">logind-check-graphical</span><span class="o">=</span><span class="s">true</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>##Edit previous commands</p> +<p><code>fc</code> is a shell builtin to list and edit previous commands in an editor. +In addition to editing a single line (which you can also do with <code>C-x C-e</code>), it also allows you to edit and run several lines at the same time. +You use it like this:</p> +<p>List previous commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10259</span> nvim deploy.sh +</span></span><span class="line"><span class="cl">10260* <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> <span class="nb">cd</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>List commands with date (in zsh)</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -ld +</span></span><span class="line"><span class="cl">10260* 19:38 <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* 19:38 nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 19:40 <span class="nb">fc</span> -l +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can add the date too:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -fld +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 1/10/2019 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 1/10/2019 19:40 <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10264</span> 1/10/2019 19:40 <span class="nb">fc</span> -ld +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can edit a range of commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> <span class="m">10262</span> <span class="m">10264</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The range can be relative to the current position, so the previous command is equivalent to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -3 -1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>If you save and exit, all commands are executed as a script, and it will be added to your history.</p> +<p>Source: <a class="link" href="https://shapeshed.com/unix-fc/" target="_blank" rel="noopener" +>https://shapeshed.com/unix-fc/</a></p> +<h2 id="prevent-logoff-from-killing-tmux-sessions">Prevent logoff from killing tmux sessions +</h2><p>Lately I&rsquo;ve noticed that logging out of i3, intentionally or when i3 fails, would also kill any tmux or emacs sessions. +This is extremely annoying.</p> +<p>This is caused by a new default in logind (systemd&rsquo;s login) to kill user process on logoff. +You can revert this setting in your logind.conf (<code>/etc/systemd/logind.conf</code>):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">KillUserProcesses</span><span class="o">=</span><span class="s">no</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Or only for a specific process (e.g., tmux):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemd-run --scope --user tmux +</span></span></code></pre></td></tr></table> +</div> +</div><p>Source: <a class="link" href="https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session" target="_blank" rel="noopener" +>https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session</a></p> +<h2 id="upload-a-temporary-file">Upload a temporary file +</h2><p>Sometimes you just need to copy/paste a file from a server, and copying from the terminal can be a hassle. +These two services are command-line &ldquo;pastebins&rdquo; just one curl away:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span><span class="lnt">8 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F <span class="s1">&#39;sprunge=&lt;-&#39;</span> http://sprunge.us +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> curl -F <span class="s1">&#39;f:1=&lt;-&#39;</span> ix.io +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F<span class="s2">&#34;file=@-&#34;</span> https://ttm.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h1 id="install-fortinet-sslvpn-support-for-networkmanager">Install Fortinet SSLVPN support for NetworkManager +</h1><p>UPM (Universidad Politécnica de Madrid) uses a propriatary VPN solution. +The instructions for GNU/Linux on their website involve downloading a specific client (<code>.tar.gz</code>) and manually running it. +That works, but it is kind of a hassle. +A much more convenient alternative is installing this NetworkManager plugin:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pacman -Sy networkmanager-fortisslvpn +</span></span><span class="line"><span class="cl"><span class="c1"># Or apt get install networkmanager-fortisslvpn </span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now you can simply add a new VPN connection in NetworkManager and manage it as you would any other connection.</p> \ No newline at end of file diff --git a/categories/linux/page/1/index.html b/categories/linux/page/1/index.html new file mode 100644 index 0000000..205d05b --- /dev/null +++ b/categories/linux/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/categories/linux/ + \ No newline at end of file diff --git a/categories/page/1/index.html b/categories/page/1/index.html new file mode 100644 index 0000000..32e2c10 --- /dev/null +++ b/categories/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/categories/ + \ No newline at end of file diff --git a/categories/programming/index.html b/categories/programming/index.html new file mode 100644 index 0000000..9a0870a --- /dev/null +++ b/categories/programming/index.html @@ -0,0 +1,33 @@ +Category: Programming - J. Fernando Sánchez +

Categories

3 pages

Programming

Posts related to programming languages

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/categories/programming/index.xml b/categories/programming/index.xml new file mode 100644 index 0000000..9b20a5e --- /dev/null +++ b/categories/programming/index.xml @@ -0,0 +1,242 @@ +Programming on J. Fernando Sánchezhttps://balkian.com/categories/programming/Recent content in Programming on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 26 Feb 2025 23:22:59 +0100Bridging RDF, JSON-LD and Dataclasseshttps://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/<p>In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.</p> +<h2 id="the-rdflib-way">The <code>rdflib</code> way +</h2><p><code>rdflib</code> only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, <code>Entity</code>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">entry</span> <span class="o">=</span> <span class="n">Entry</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">entry</span><span class="p">[</span><span class="s1">&#39;vocab:property&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">jsonld</span><span class="p">())</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Would print something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@id&#34;</span><span class="p">:</span> <span class="s2">&#34;:Entry_202505....&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;prefix:Entity&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;vocab:property&#34;</span><span class="p">:</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the</p> +<p>Pros:</p> +<ul> +<li>Flexible/extensible</li> +<li>Lightweight. This is mostly JSON-LD in Python&rsquo;s clothing.</li> +<li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li> +</ul> +<p>Cons:</p> +<ul> +<li>Discoverability. Documentation and examples are needed to know which attributes to use</li> +<li>Error-prone. It is easy to misuse a property, or introduce typos</li> +<li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li> +</ul> +<h2 id="the-object-oriented-way">The object-oriented way +</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like <a class="link" href="https://pydantic.dev/" target="_blank" rel="noopener" +>pydantic</a> that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p> +<p>This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p> +<p>Pros:</p> +<ul> +<li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li> +<li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li> +</ul> +<p>Cons:</p> +<ul> +<li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li> +<li>Polymorphism.</li> +</ul> +<h2 id="a-hybrid-approach">A hybrid approach +</h2><p>Whichever solution is chosen in the end, it needs to:</p> +<ul> +<li>Make it easy and error-proof to add the most common types of annotations</li> +<li>Allow for additional annotations/attributes to be added</li> +<li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li> +<li>Allow for deserialization of custom types</li> +<li>Allow multiple consumers to add their own annotations</li> +</ul>uv - One rust tool to rule all pythonshttps://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/Mon, 17 Feb 2025 23:02:47 +0100https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/<img src="https://balkian.com/img/uv.png" alt="Featured image of post uv - One rust tool to rule all pythons" /><p>Long story short: I&rsquo;m now using <a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a>, and so should you. +It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.</p> +<h2 id="context">Context +</h2><p>For years, my strategy to manage python projects has been a mix of a custom <code>setup.py</code>, several hand-crafted <code>requirements.txt</code> files (through <code>pip freeze</code>), a custom virtualenv per project, and multiple tools to upload to PyPI. +Although this works, this setup has many drawbacks:</p> +<ul> +<li>It requires user intervention (creating a venv, sourcing it, handling new deps). This isn&rsquo;t ideal if you want new (probably inexperienced) users to use your projects.</li> +<li>On a similar note, the whole process needs to be well documented if you want other users to contribute or maintain the code.</li> +<li>Pinning dependency versions is finicky, and I&rsquo;ve run into problems beause of that.</li> +<li>Creating a new project involves a template, or copying files from an older project.</li> +</ul> +<p>Of course, this is nothing new. +There is a whole site dedicated to <a class="link" href="https://packaging.python.org/en/latest/" target="_blank" rel="noopener" +>packaging your Python project</a>. +A plethora of different projects have come and go, with varying degrees of success.</p> +<h2 id="alternatives-poetry">Alternatives (poetry) +</h2><p>About a year before trying <code>uv</code>, I tried to catch up with the ecosystem and get to know the <code>blessed new way</code>. +However, the task proved to be a little more difficult, as the landscape is filled with a myriad of alternatives, each with their own set of drawbacks and detractors. +Packaging has historically been a weak spot, in ironical contradiction to the Zen of Python&rsquo;s &ldquo;There should be one&ndash; and preferably only one &ndash;obvious way to do it&rdquo;,</p> +<p>I eventually settled on <a class="link" href="https://python-poetry.org/" target="_blank" rel="noopener" +>poetry</a>. +Mostly because it seemed like the most popular alternative.</p> +<p>There are many things I liked about it. +First of all, having a convention for dependencies (<code>pyproject.toml</code>) and a tool that properly handles them was nice. +It also removed the need to remember specific incantations to build and publish my Python projects. +Lastly, I mixed it <code>poetry2nix</code> to create reproducible python environments using nix. +This makes for a very powerful experience.</p> +<p>However, there were multiple hiccups. +First of all, it took me some time to figure out which specific fields to use (each tool can define ad-hoc properties in a the <code>pyproject.toml</code> file), and some of them seemed redundant with the more generic ones. +Full disclosure, this specific point might be a mistake on my side, and I do not remember the details. +The second one is speed. +(Re-)creating an environment took a non-negligible amount of time.</p> +<h2 id="enter-light-uv">Enter <del>light</del> <code>uv</code> +</h2><p>According to its repository, <code>uv </code>can replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +Not only that, but it also claims to do that 10-100 times faster than pip. +I must admit that it being written in rust was a another selling point for me, as I&rsquo;m looking for excuses to collaborate in a decently-sized rust projejct.</p> +<p>Installing it is dead simple: simply download the binary (e.g., with curl) or run <code>pip install uv</code>. +You won&rsquo;t need much more: <code>uv</code> seems to just do the right thing out of the box. +And it does it really, really fast. +The rest of the time it gets out of the way.</p> +<p>My only gripe so far is that I don&rsquo;t seem to find a built-in command to drop into a shell, but that is nothing that <code>uv run $SHELL</code> cannot fix.</p> +<h2 id="common-operations">Common operations +</h2><h3 id="initialize-a-repository">Initialize a repository +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv init +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="adding-dependencies">Adding dependencies +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv add senpy +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="running-commands-inside-the-environment">Running commands inside the environment +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv run &lt;COMMAND&gt; +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"># e.g., run a shell using your python version and dependencies +</span></span><span class="line"><span class="cl">uv run $SHELL +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="dependency-tree">Dependency tree +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv shell +</span></span><span class="line"><span class="cl">Resolved 44 packages in 1ms +</span></span><span class="line"><span class="cl">my-project v0.1.0 +</span></span><span class="line"><span class="cl">├── fastapi[standard] v0.115.8 +</span></span><span class="line"><span class="cl">│ ├── pydantic v2.10.6 +</span></span><span class="line"><span class="cl">│ │ ├── annotated-types v0.7.0 +</span></span><span class="line"><span class="cl">│ │ ├── pydantic-core v2.27.2 +</span></span><span class="line"><span class="cl">│ │ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── starlette v0.45.3 +</span></span><span class="line"><span class="cl">│ │ └── anyio v4.8.0 +</span></span><span class="line"><span class="cl">│ │ ├── exceptiongroup v1.2.2 +</span></span><span class="line"><span class="cl">│ │ ├── idna v3.10 +</span></span><span class="line"><span class="cl">│ │ ├── sniffio v1.3.1 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── email-validator v2.2.0 (extra: standard) +</span></span><span class="line"><span class="cl">│ │ ├── dnspython v2.7.0 +</span></span><span class="line"><span class="cl">... +</span></span></code></pre></td></tr></table> +</div> +</div>Pythonhttps://balkian.com/cheatsheet/python/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/python/<img src="https://balkian.com/img/python.png" alt="Featured image of post Python" /><h2 id="interesting-libraries">Interesting libraries +</h2><h3 id="tqdm"><a class="link" href="https://github.com/tqdm/tqdm" target="_blank" rel="noopener" +>TQDM</a> +</h3><p>From tqdm&rsquo;s github repository:</p> +<blockquote> +<p>tqdm means &ldquo;progress&rdquo; in Arabic (taqadum, تقدّم) and an abbreviation for &ldquo;I love you so much&rdquo; in Spanish (te quiero demasiado).</p></blockquote> +<p><img src="https://raw.githubusercontent.com/tqdm/tqdm/master/images/tqdm.gif" +loading="lazy" +alt="TQDM in action" +></p> +<h2 id="tools">Tools +</h2><h3 id="uv"><a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a> +</h3><p>🚀 A single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +⚡️ 10-100x faster than pip.</p> +<ul> +<li>Provides comprehensive project management, with a universal lockfile.</li> +<li>Runs scripts, with support for inline dependency metadata.</li> +<li>Installs and manages Python versions.</li> +<li>Runs and installs tools published as Python packages.</li> +<li>Includes a pip-compatible interface for a performance boost with a familiar CLI.</li> +<li>Supports Cargo-style workspaces for scalable projects.</li> +<li>Disk-space efficient, with a global cache for dependency deduplication.</li> +<li>Installable without Rust or Python via curl or pip.</li> +<li>Supports macOS, Linux, and Windows.</li> +</ul> \ No newline at end of file diff --git a/categories/programming/page/1/index.html b/categories/programming/page/1/index.html new file mode 100644 index 0000000..455331e --- /dev/null +++ b/categories/programming/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/categories/programming/ + \ No newline at end of file diff --git a/categories/reflections/index.html b/categories/reflections/index.html new file mode 100644 index 0000000..8e62af9 --- /dev/null +++ b/categories/reflections/index.html @@ -0,0 +1,33 @@ +Category: Reflections - J. Fernando Sánchez +

Categories

1 page

Reflections

Longer posts that summarize my views or thoughts on a specific topic

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/categories/reflections/index.xml b/categories/reflections/index.xml new file mode 100644 index 0000000..b15fb8e --- /dev/null +++ b/categories/reflections/index.xml @@ -0,0 +1,327 @@ +Reflections on J. Fernando Sánchezhttps://balkian.com/categories/reflections/Recent content in Reflections on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 05 Mar 2025 09:25:54 +0100Tips for efficient collaborationhttps://balkian.com/p/efficient-collaboration/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/p/efficient-collaboration/<h2 id="background">Background +</h2><blockquote class="note"><p>TL;DR I work in academia. This post focuses on advice I&rsquo;d give a younger me to be a more effective supervisor and project lead.</p></blockquote> +<p>My role in my research group has evolved from individual contributor to project lead that manages a team of multiple students. +This often involves coordinatating with other senior researchers and their teams.</p> +<p>This post is a collection of advice I would have given myself back when I started this journey. +It is also an excuse to reflect on these ideas I&rsquo;ve been implicitly applying everyday, and maybe learn a few things more in the process.</p> +<p>In my field, projects are often tied to a specific grant or some sort of public funding. +This means that the main concern of the lead is to ensure that the results at the end of the project match the description in the project proposal. +It also typically means maximizing the number of publications related to the project and their overall impact.</p> +<p>To do so, most projects rely on three types of staff: a) senior researchers (post-doc); b) junior researchers (PhD students); and c) interns doing their bachelor&rsquo;s or master&rsquo;s thesis. +The level of contribution is generally inversely propotional to the level of mastery of the contributor: PhD students design and develop the main contributions (both software and experimental) under the supervision of senior researchers (advisor/supervisor), and interns take care of tasks that are narrow in scope and not crucial to any academic contribution. +For instance, a PhD student may develop a new model for text classification, and an intern will wrap that model in an HTTP service with a nice UI. +When the service and UI part is intricate and has some potential academic merit, that task may be conducted by a PhD student as part of their thesis. +That was precisely the case with Senpy, which was part of my PhD thesis, and it has since been used by dozens of students to develop services in the context of other research projects.</p> +<h2 id="reasons-to-form-a-team">Reasons to form a team +</h2><p>In my opinion, a team has two advantages over a single contributor. +The first one is that collaboration often generates <strong>synergies</strong>, leading to surprising and enriching results (a team is greater than the sum of its parts) +Carefully selecting your team members and creating an environment that is conducing to these synergies is a topic on its own, and I will not get too deep into it here.</p> +<p>The second advantage can be summarized as <strong>concurrency</strong>: tasks can be tackled by more than one member. +This often implies some sort of parallelism. +Tasks tend to be split between different members, in hopes of speeding up the process. +But it can also be beneficial to have concurrency even in the absence of parallelism: different members can take turns solving the same problem. +This is common when the task requires exclusive access to a resource (e.g., performing an experiment on an expensive machine).</p> +<h2 id="challenges-of-teams-of-students">Challenges of teams (of students) +</h2><p>Just like in computer science, coordination in a concurrent project involves a non-negligible overhead. +The objective is to minimize this overhead.</p> +<p>I&rsquo;ve really struggled with managing teams in the past. +Given my context, I often attributed the failures to lack of time (having to juggle teaching and research), lack of training and preparation on the intern&rsquo;s side (they&rsquo;re often undergrads), lack of appeal or definition of the task (too academic-y), or any other external factors.</p> +<p>While all those aspects play an important role, some of them are out of our control as a project lead in academia: grants require a certain type of project and workflow, and the quality of our interns is bounded by the quality of our degrees. +So I think it is more constructive to focus on things that we can control. +In other words: we have to play the hand we&rsquo;re given.</p> +<p>Besides, there is no merit in achieving good results with excellent students/engineers. +They would succeed on their own even if you weren&rsquo;t there. +The real test for a good leader is succeeding with a subpar team.</p> +<p>In that vein, I&rsquo;ve reflected on my mistakes as a leader, and the inefficiencies of the teams around me. +I&rsquo;d classify my failures in the following areas:</p> +<ul> +<li>Delegation (and lack thereof). Piling up too many tasks and blocking progress.</li> +<li>Communication. Not having a coherent view of the state of affairs, the details of specific tasks, the general processes to follow, or the priorities of different tasks within a project.</li> +<li>Direction (or purpose). Not having a common direction</li> +</ul> +<h3 id="delegation">Delegation +</h3><p>I have a tendency to become a bottleneck in any project I am involved in: many tasks end up depending on me, either directly or indirectly. In the concurrency metaphor, I become a lock for many tasks, and a single executor for the rest. +This issue was not that apparent early on, when most of my work was as an individual contributor or I had the bandwidth to supervise and complete my tasks.</p> +<p>I think it is quite common to feel like delegating a task in these scenarios means:</p> +<ol> +<li>Defining the task in advance</li> +<li>Choosing an assignee for the task</li> +<li>Setting a deadline for the task</li> +<li>Explaining the task and the relevant context</li> +<li>Replying to multiple questions by e-mail or in person. Extra points when the questions make you wonder if any part of the explanation was ever clear.</li> +<li>Reviewing the results after the deadline</li> +<li>Realizing the assignee misunderstood the task or delivered something not even close to what you agreed upon</li> +<li>Going back to point 3.</li> +<li>When you&rsquo;re unlucky or short on time: giving up and doing the task yourself</li> +</ol> +<p>Many times, if felt like delegating tasks only lead to frustration and wasted time. +Especially when compared to the alternative:</p> +<ul> +<li>Defining the task</li> +<li>Setting a deadline</li> +<li>Finishing the task</li> +<li>Profit</li> +</ul> +<p>Luckily, some students and projects were an exception to this. +They worked autonomously and delivered something beyond the minimum requirements. +This reinforced my helplessness and the feeling that the problem was not being able to work with experienced engineers.</p> +<p>However, I now believe that the truth lies somewhere in between. +Sometimes your circumstances make it quite hard or inefficient to delegate tasks. +And some times may not be good candidates for delegation. +But most of the times you can take advantage of having an extra pair of hands, you just have to do that effectively.</p> +<h3 id="communication">Communication +</h3><p>Small teams rely on implicit knowledge more than they realize. +Even more so if the team is made up of highly specialized people that have worked in the same environment with mostly the same people for years.</p> +<p>Communication is a broad term. +It includes technical and concrete things such as how a certain task should be done. +But it also includes broader things like etiquette, organizational values, and who is more willing to help you out on certain topics on a Friday afternoon.</p> +<p>Here, I would take a page out of Python&rsquo;s zen and recommend that &ldquo;explicit is better than implicit&rdquo;. +Implicit (or tacit) knowledge comes with a whole set of drawbacks:</p> +<ul> +<li>It makes onboarding new users harder. Without a common knowledge base, all knowledge transference has to rely on personal interactions. Even worse, those interactions are probably organized on the spot, and are likely to miss important points.</li> +<li>It makes you heavily reliant on your current members (and their memory).</li> +<li>It impedes proper evaluationn and progress, since they are not written anywhere.</li> +<li>It increases the likelihood of misunderstandings when two members have conflicting beliefs, and makes it harder to detect them until it is too late.</li> +<li>It makes contradictions and (unknowingly) changing your mind much more likely. It can happen to the best of us, especially if you are involved in too many projects. When contraditions happen often, your colleagues will learn not to rely on your opinion.</li> +</ul> +<p>On the other hand, communication has to go both ways. +This means that your newer members need to be able to communicate when something is going wrong or can be improved (backpressure). +They should also feel free to talk about their motivation, state of mind, and feelings, <strong>when appropriate</strong>. +That last part is quite subjective, of course. +Try to find your - and your organization&rsquo;s - middle ground between &ldquo;I don&rsquo;t care how you feel, just do your job&rdquo; and &ldquo;sure, you can go to the Maldives on short notice. Oh, and don&rsquo;t worry about not having met a deadline in months, I&rsquo;m sure you&rsquo;re stressed and can use some vacation but will work remotely if we need you&rdquo;.</p> +<p>I personally feel a sweet spot is treating your coworkers like people, being empathetic and compassionate. +Part of being a good coworker is fulfilling the duties and obligations you accepted when signing your contract. +First and foremost, because not fulfilling them means someone else will have to work harder to make up for it. +And, secondly, because doing our part is the only way to move the organization (and research) forward.</p> +<h3 id="direction">Direction +</h3><p>By failure in direction I mean not keeping a consistent and shared set of general goals, principles and values in your organization. +In order to really take part in any enterprise, you need to have a clear understanding of the objectives and motivation. +When it comes to specific tasks, <em>what you&rsquo;re doing</em> is often not as important as the <em>why you&rsquo;re doing it</em>. +In fact, there may be times where you aren&rsquo;t truly sure of what exactly it is that you are doing, but you trust the process and the motivation behind the task.</p> +<p>I&rsquo;ve seen two failure modes in this regard. +The first one is to not have a clear direction. +The end result is that members of the team are not really that committed. +If no other <em>why</em> is provided, we are only left with <em>because they pay me to do it</em>. +And academia is not known to pay particularly well, to be honest.</p> +<p>The other mode is to provide contradicting or incompatible directions. +This can be in a short period of time, leading to the impression that there isn&rsquo;t really any conviction in the message. +But it can also be done over a longer period of time. +That can be perfectly acceptable, provided that the change in direction is justified and compatible with the principles of the organization.</p> +<p>Failure in direction is somewhat related to communication, but it is subtly different. +An organization can excel at communication, but change their direction constantly. +Arguably, a thorough communication strategy makes radical changes in direction less likely. +On the one hand, a change in direction needs to be documented, which can be a pain. +On the other hand, a written change is easier to spot and more likely to generate complaints.</p> +<h2 id="rules">Rules +</h2><p>I&rsquo;d argue that the path to successfully managing a research team lies in roughly the following key goals:</p> +<ul> +<li>Fostering autonomy</li> +<li>Avoiding miscommunication</li> +<li>Optimizing your contribution</li> +</ul> +<p>The remaining of the post will be a series of tasks or rules to achieve these goals.</p> +<blockquote class="warning"><p>Most of these ideas probably generalize well to collaboration outside of academia, but I hesitate to make more general claims.</p></blockquote> +<h3 id="fostering-automony">Fostering automony +</h3><p>The tips here are aimed at avoiding supervision overhead and training future leads.</p> +<h4 id="provide-a-simplified-version-of-the-bigger-picture">Provide a (simplified version of the) bigger picture +</h4><p>Try to paint the bigger picture, even for menial tasks within large projects. +For you, this may be the nth project you&rsquo;re involved in this year, but the new intern may not have even heard about European projects before. +Going back to the idea of direction, it is easier to work on something if you know the context of your work.</p> +<p>Having a general idea of the project and the context of your task will also help you make decisions on your own. +For instance, if I am told to develop a shiny new API for text classification, I may have to ask many questions: 1) what will be input look like?; 2) what should the parameters be?; 3) am I using POST or GET requests?; 4) should I return a JSON object or an XML?&hellip; +What if, instead of that, I am also told this API will be used in the context of project X, that our organization will be the only consumers of the API, and they also give me a link to the project&rsquo;s docs. +I may be able to figure out some of those answers on my own (e.g., by finding examples in the project&rsquo;s website), or decide that some questions are not vital at this point (e.g., if we are the only consumers, we can change from GET to POST if we need to much more easily).</p> +<p>One caveat here is that a link to the documentation or some vague words about the project do not constitute proper context. +You are responsible for summarizing the important bits of the context, providing instructions on how to navigate the reference materials, and being open to answer questions that may arise in the exchange.</p> +<h4 id="do-not-discuss-implementation-details-unless-strictly-necessary">Do not discuss implementation details unless strictly necessary +</h4><p>There is a fine line between discussing a non-trivial implementation detail and bikeshedding for hours about class names and code best practices. +For that reason, you should try to prioritize discussing high-level parts of the problem and the assignment, and trust the student to figure out the details on their own, or come back to you for clarification.</p> +<p>It is very common that students focus on very specific details when they are sharing their progress with their supervisors. +They will generally try to start by showing snippets of code and their results. +I find it helpful to remind them to explain their problems top-to-bottom, starting with a sentence or two about the context of their project, the description and motivation of the specific task they were doing, and the relationship with previous (and future) tasks. +That usually helps figure out the level of understanding of the student, whether there are any conceptual errors, and whether the specific block or problem is really worth discussing during the meeting.</p> +<p>Some technical problems will warrant a discussion in detail, either due to their complexity or their importance to the project. +In those cases, always limit the time you will spend on that specific issue ahead of time, and make sure to allow for some time at the end of the meeting to go back to any important high-level details.</p> +<p>If there are other students that worked on similar projects, do not hesitate to refer your new student to them. +It can be an opportunity for them to collaborate, and for the original student to work on explaining and teaching technical issues.</p> +<h4 id="provide-feedback">Provide feedback +</h4><p>Make a point of evaluating the results of each student on every level, and provide constructive and actionable feedback to them. +Even if no technical issues arise during the project, try to review the code and give some tips (e.g., formatting, code structure, DRY). +Try to focus on bigger issues and enforcing best practices before nitpicking and giving feedback on small subjective improvements.</p> +<p>Make it clear when your feedback is objective/best practice (e.g., a function is deprecated) and when it is a matter of preference. +If it is the latter, try to provide more than one alternative, to encourage them to think about it and make an educated decision.</p> +<h4 id="take-documentation-and-knowledge-transfer-seriously">Take documentation and knowledge transfer seriously +</h4><p>Taking the time to write down basic documentation can save a lot of time in the long run. +Besides, most of the job of mentoring a new student is lost when that student finishes their degree and leaves to find a job in industry. +Good documentation can remain in your organization and be extended long after the intern is gone.</p> +<p>This is very obvious for specific tools, whether internal or public. +Good documentation means any new member can check the tool and use it without much assistance. +Even better documentation helps newcomers contribute to the tool. +Make sure to make it clear who to approach if something is missing from the documentation, and make it easier to do so than to make assumptions and use the tool incorrectly.</p> +<p>Writing documentation can be very time consuming, and sometimes it is hard to know exactly what things to focus on when writing the docs. +You need to anticipate the needs of the future user. +If you are short on time, a good strategy is to delegate the writing of the documentation. +Instead of going into details, you can write a very barebones version and training a new user to use and contribute to your tool. +Then, leave it up to the new user to extend the documentation, including more details and pitfalls. +As a bonus, reading and fixing the docs will give you a better sense of how well that new user understands the tool, as well as possible improvements.</p> +<p>This tip also applies to more general areas such as machine learning, graph neural networks, or simulation. +Just remember you do not need to reinvent the wheel in those cases. +A simple summary and a list of references to expand on the topic could be more than enough. +Make sure to also include any specifics that apply to your organization. +For instance, point to repositories on github (public or private) that can be used to explore the topic, examples of similar projects in the domain, etc.</p> +<p>Identify what information is important for any new hire and present it to them as clearly as possible. +Part of that information should be where and how common knowledge is stored and shared, should they need more information in the future. +Make this documentation as easy to discover and consume as possible. +Centralizing this common information in the form of a wiki is often a good idea.</p> +<p>Lastly, make it easy for any member of your organization to update this common documentation, and encourage them to do so. +Whenever a member asks you something useful that is not documented, don&rsquo;t just answer the question. +Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer. +If your organization&rsquo;s culture does not encourage using these docs, they will quickly get outdated and fall out of use.</p> +<p>One example of taking this documentation approach really seriously is <a class="link" href="https://oxide.computer/" target="_blank" rel="noopener" +>Oxide (computer company)</a>. +They have a process they call <a class="link" href="https://rfd.shared.oxide.computer/" target="_blank" rel="noopener" +>Request For Discussion (RFD)</a>, which they use to discuss and document both technical and organizational decisions. +For instance, they have <a class="link" href="https://rfd.shared.oxide.computer/rfd/0537" target="_blank" rel="noopener" +>RFDs on why they record every meeting</a>, <a class="link" href="https://rfd.shared.oxide.computer/rfd/0110" target="_blank" rel="noopener" +>RFDs about their choice of database</a>, and even <a class="link" href="https://rfd.shared.oxide.computer/rfd/0001" target="_blank" rel="noopener" +>an meta-RFD that discusses the motivation RFDs and how the process should work</a>.</p> +<h4 id="trust-your-teammates-ability-to-learn">Trust your teammate&rsquo;s ability to learn +</h4><p>I&rsquo;ve been bitten by this way too many times. +Your students are probably more capable of learning than you think, especially if you have set up your documentation right. +What they lack in experience, they make up for with free time, a (more) neuroplasticity and determination.</p> +<p>Sure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.</p> +<h4 id="use-tools-wisely">Use tools wisely +</h4><p>Your students probably have little experience with code versioning, reviewing processes, time management, etc. +A good choice of tools and some training can go a long way and make your life much easier in the long run. +It will also give your students a taste of what working in a bigger/real company feels like and a head start.</p> +<p>For instance, using git makes it easier to collaborate on code. +It also ensures that your results will not be lost if your student&rsquo;s laptop gets stolen.</p> +<p>Using GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students. +It will force them to commit working code, and it will make it easier to check their results and discuss the end result.</p> +<p>Using overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results. +You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration. +In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.</p> +<p>Also, on a related note, make sure every team member has a proper development setup. +It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work. +It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.</p> +<h4 id="encourage-cooperation">Encourage cooperation +</h4><p>Do not become the center of every conversation. +If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.</p> +<p>The ability to discuss with your peers and report only when needed will be extremely important for them in the future. +They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are). +That might lead to valuable insights and improvements for your team and project.</p> +<p>Moreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.</p> +<h4 id="reward-proactivity">Reward proactivity +</h4><p>The whole point of this section is to get your team to work independently when possible. +Be explicit about this goal to make sure it is clear to everyone. +And encourage behavior that aligns with this goal, even on a small scale.</p> +<p>For instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements. +Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time. +Do not jump straight to criticize it. +Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.</p> +<h4 id="dont-be-a-perfectionist">Don&rsquo;t be a perfectionist +</h4><p>Perfect is the enemy of done. +It is also the enemy of a happy co-worker.</p> +<p>Try to remember that you are dealing with students, and you were probably no better at their age. +Besides, you probably delegated the task beause you did not have any spare time to do it yourself. +<code>FIXME</code> is often better than <code>TODO</code>.</p> +<p>Take the opportunity to provide some feedback and teach them something useful. +Some mistakes are also worth adding to your documentation, or presenting to other students in a presentation.</p> +<h3 id="avoiding-miscommunication">Avoiding miscommunication +</h3><p>A common source of wasted effort and unnecessary back-and-forth is miscommunication. +These are some tips to help keep everyone on the team informed and aligned.</p> +<h4 id="make-priorities-clear">Make priorities clear +</h4><p>All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks. +This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.</p> +<h4 id="define-boundaries-and-abstractions">Define boundaries (and abstractions) +</h4><p>Once again, the goal is generally to achieve some sort of parallelism between your team members. +In order to do that, they need to know how they will interact with each other.</p> +<p>On a more general level, this means knowing the responsibilities and scope of your work.</p> +<p>On a more specific level, it means knowing their dependency graph. +In other words, whether the progress of one team member will depend on the results of another one. +Whenever there is a dependency, the interface should be made very clear. +This often takes the form of an API, a file with a given format, or a section of a document.</p> +<p>Take some time to define the boundary as precisely as needed at that point in the project. +I would suggest having specific examples that you can discuss and modify. +It is hard to discuss in the abstract, especially for inexperienced contributors. +When in doubt, default to the simplest option (e.g., a common file vs using a database). +Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there. +Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.</p> +<p>One type of failure I&rsquo;ve seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples. +That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.</p> +<h4 id="be-approachable">Be approachable +</h4><p>Did I wrote a whole section about autonomy? Yes. +Is the end goal to do more and talk less? Also yes. +Thing is, no process is perfect, and misunderstandings are bound to happen at some point. +If your only response to questions is a grumpy face or a &ldquo;read the freaking docs&rdquo;, your students will not alert you when something really needs your attention, and you will find out too late. +For instance, the documentation may be unclear, or your processes may be inadvertedly alienating new members and making new hires harder.</p> +<p>Another way to be approachable is to be clear about your shortcomings, and whether something you are saying is negotiable and/or debatable. +My rule of thumb is to err on the side of negotiable, and only be strict when it is really necessary (e.g., time constraints or an unproductive student). +We are all more likely to finish our tasks if we feel them ours, if we a say in how and when to perform them.</p> +<p>Just to be clear, approachable does not mean you have to be their confident or their best friend. +It also does not mean that it is okay to challenge or question you continuously. +Some times it is okay to simply say &ldquo;just do as I say&rdquo;.</p> +<h4 id="review-frequently">Review frequently +</h4><p>One type of review is individual. +It involves reviewing code on github, or reading deliverables and papers on overleaf. +It can help catch misunderstandings, and measure the true rate of progress in the individual tasks. +The other type of review is done as a group, by going through the key progress and action points. +This type of review helps everyone stay on the same page, and catch any general drifts in the project, such as misaligned priorities.</p> +<p>The frequency of each type of review depends on the specific nature of the project, the types of tasks being performed by the student, and your confidence on the student&rsquo;s abilities.</p> +<h3 id="optimizing-your-contribution">Optimizing your contribution +</h3><p>Tips on optimizing your contribution to the team.</p> +<h4 id="prioritize-prioritize-prioritize">Prioritize, prioritize, prioritize +</h4><p>Part of your job as a project lead is to identify the main goals in a project and to prioritize the tasks that will lead you there. +On the other hand, you are part of a research group, and you should be actively involved in its health and future. +Lastly, you are also in charge of the life-long project that is your research career.</p> +<p>In all these cases, your goal should be to identify the long term goals, come up with a sound strategy, and prioritize the tasks that will lead you and your group there. +Keeping your priorities straight will help you make steady progress, and avoid bikeshedding and changing goalposts. +It will also help you steer your progress in the right direction, since we all have limited time and effort and can&rsquo;t do everything at once.</p> +<p>The fact that your time is limited also means that you will need to decide how to prioritize these three roles. +I&rsquo;ve listed them in increasing level of importance for me. +It means that it is okay to focus on a specific project for a while, but if progress in your career is stalled - usually through publication - you need to reevaluate and concentrate your efforts on that.</p> +<h4 id="be-okay-with-short-term-inefficiencies">Be okay with (short-term) inefficiencies +</h4><p>I&rsquo;ve personally struggled with delegating tasks that will take me orders of magnitude less work than they will a student. +Thing is, most tasks will fall under this category, and your time is limited, so you have to delegate if you want to have time for more important matters. +If you never delegate any tasks, you are not allowing your team to learn and catch up on whatever technical skills are required. +Besides, you are not improving yourself on the managerial side of things. +It turns out delegating is hard, it requires a whole set of non-technical skills. +I suspect this is oftentimes the reason we don&rsquo;t do delegate in the first place: delegating is hard, and technical tasks are usually more straightforward, so we just don&rsquo;t want to do the work.</p> +<h4 id="dont-neglect-training">Don&rsquo;t neglect training +</h4><p>You are a senior researcher. +You probably know how to solve problems in your domain quite efficiently. +In my case, that means processing data and developing code.</p> +<p>That means I could dedicate my days to processing data and developing new code for my group. +That group would likely be used in multiple projects. +However, there is a hard limit to how much code I can push out in a day, especially if you take into account other obligations such as teaching.</p> +<p>A wiser strategy would be to set aside some of that coding time to instead help students become better programmers. +Firstly, because those students will be thankful and more motivated to work than when they are left to learn on their own without much guidance. +Secondly, because those students will then be more prepared to help me out if I delegate a task to them. +And, lastly, because these students have a whole life in fron of them. +A life full of big projects of their own, and contributions to society. +That little training time can have a compounding effect in the future.</p> +<h4 id="set-a-time-limit-for-your-interactions-in-advance">Set a time limit for your interactions in advance +</h4><p>Really long slots can easily lead to bikeshedding and going unnecessarily deep into implementation details, which is clearly an inefficient use of your time. +Even worse, our attention span and memory are finite, so longer and dense meetings can lead to fatigue and to missing or dilluting important points in the conversation.</p> +<p>For these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary. +You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.</p> +<h2 id="beyond-your-team">Beyond your team +</h2><p>The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control. +But teams rarely work in isolation, you will most likely +In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization&rsquo;s culture and sense of belonging.</p> +<p>Many of the aspects I talked about in the team section apply here. +For instance, the obsession with documentation can - and should - be applied organization-wise. +The same goes for defining boundaries and using concrete examples when collaborating with other teams. +For most intents and purposes, you can treat other teams as another contributor to your team. +Just one that will be more costly and slow to interact with.</p> +<p>If possible, I&rsquo;d try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved. +Avoid involving whole teams in discussions when the broad strokes have not been defined yet. +The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.</p> +<p>On the organization&rsquo;s side, I would suggest having an honest conversation about your core principles. +I really liked <a class="link" href="https://www.youtube.com/watch?v=9QMGAtxUlAchttps://www.youtube.com/watch?v=9QMGAtxUlAc" target="_blank" rel="noopener" +>Bryan Cantrill&rsquo;s talk about principles of technology leadership</a>. +He goes deep into the effects that principles have had on well known companies, and how to go about defining your company&rsquo;s principles. +I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.</p> +<p>More generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive. +And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier. +Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.</p> \ No newline at end of file diff --git a/categories/reflections/page/1/index.html b/categories/reflections/page/1/index.html new file mode 100644 index 0000000..a8d54ee --- /dev/null +++ b/categories/reflections/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/categories/reflections/ + \ No newline at end of file diff --git a/cheatsheet/index.html b/cheatsheet/index.html new file mode 100644 index 0000000..ccb6d1a --- /dev/null +++ b/cheatsheet/index.html @@ -0,0 +1,33 @@ +Cheatsheets +

Section

2 pages

Cheatsheets

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/cheatsheet/index.xml b/cheatsheet/index.xml new file mode 100644 index 0000000..519bd74 --- /dev/null +++ b/cheatsheet/index.xml @@ -0,0 +1,176 @@ +Cheatsheets on J. Fernando Sánchezhttps://balkian.com/cheatsheet/Recent content in Cheatsheets on J. Fernando SánchezHugo -- gohugo.ioen-usLinux Cheatsheethttps://balkian.com/cheatsheet/linux/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/linux/<img src="https://balkian.com/img/linux.png" alt="Featured image of post Linux Cheatsheet" /><h2 id="black-screen-and-lightdm-doesnt-unlock">Black screen and LightDM doesn&rsquo;t unlock +</h2><p>Add this to your /etc/lightdm/lightdm.conf file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[LightDM]</span> +</span></span><span class="line"><span class="cl"><span class="na">logind-check-graphical</span><span class="o">=</span><span class="s">true</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>##Edit previous commands</p> +<p><code>fc</code> is a shell builtin to list and edit previous commands in an editor. +In addition to editing a single line (which you can also do with <code>C-x C-e</code>), it also allows you to edit and run several lines at the same time. +You use it like this:</p> +<p>List previous commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10259</span> nvim deploy.sh +</span></span><span class="line"><span class="cl">10260* <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> <span class="nb">cd</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>List commands with date (in zsh)</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -ld +</span></span><span class="line"><span class="cl">10260* 19:38 <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* 19:38 nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 19:40 <span class="nb">fc</span> -l +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can add the date too:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -fld +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 1/10/2019 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 1/10/2019 19:40 <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10264</span> 1/10/2019 19:40 <span class="nb">fc</span> -ld +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can edit a range of commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> <span class="m">10262</span> <span class="m">10264</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The range can be relative to the current position, so the previous command is equivalent to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -3 -1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>If you save and exit, all commands are executed as a script, and it will be added to your history.</p> +<p>Source: <a class="link" href="https://shapeshed.com/unix-fc/" target="_blank" rel="noopener" +>https://shapeshed.com/unix-fc/</a></p> +<h2 id="prevent-logoff-from-killing-tmux-sessions">Prevent logoff from killing tmux sessions +</h2><p>Lately I&rsquo;ve noticed that logging out of i3, intentionally or when i3 fails, would also kill any tmux or emacs sessions. +This is extremely annoying.</p> +<p>This is caused by a new default in logind (systemd&rsquo;s login) to kill user process on logoff. +You can revert this setting in your logind.conf (<code>/etc/systemd/logind.conf</code>):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">KillUserProcesses</span><span class="o">=</span><span class="s">no</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Or only for a specific process (e.g., tmux):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemd-run --scope --user tmux +</span></span></code></pre></td></tr></table> +</div> +</div><p>Source: <a class="link" href="https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session" target="_blank" rel="noopener" +>https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session</a></p> +<h2 id="upload-a-temporary-file">Upload a temporary file +</h2><p>Sometimes you just need to copy/paste a file from a server, and copying from the terminal can be a hassle. +These two services are command-line &ldquo;pastebins&rdquo; just one curl away:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span><span class="lnt">8 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F <span class="s1">&#39;sprunge=&lt;-&#39;</span> http://sprunge.us +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> curl -F <span class="s1">&#39;f:1=&lt;-&#39;</span> ix.io +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F<span class="s2">&#34;file=@-&#34;</span> https://ttm.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h1 id="install-fortinet-sslvpn-support-for-networkmanager">Install Fortinet SSLVPN support for NetworkManager +</h1><p>UPM (Universidad Politécnica de Madrid) uses a propriatary VPN solution. +The instructions for GNU/Linux on their website involve downloading a specific client (<code>.tar.gz</code>) and manually running it. +That works, but it is kind of a hassle. +A much more convenient alternative is installing this NetworkManager plugin:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pacman -Sy networkmanager-fortisslvpn +</span></span><span class="line"><span class="cl"><span class="c1"># Or apt get install networkmanager-fortisslvpn </span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now you can simply add a new VPN connection in NetworkManager and manage it as you would any other connection.</p>Pythonhttps://balkian.com/cheatsheet/python/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/python/<img src="https://balkian.com/img/python.png" alt="Featured image of post Python" /><h2 id="interesting-libraries">Interesting libraries +</h2><h3 id="tqdm"><a class="link" href="https://github.com/tqdm/tqdm" target="_blank" rel="noopener" +>TQDM</a> +</h3><p>From tqdm&rsquo;s github repository:</p> +<blockquote> +<p>tqdm means &ldquo;progress&rdquo; in Arabic (taqadum, تقدّم) and an abbreviation for &ldquo;I love you so much&rdquo; in Spanish (te quiero demasiado).</p></blockquote> +<p><img src="https://raw.githubusercontent.com/tqdm/tqdm/master/images/tqdm.gif" +loading="lazy" +alt="TQDM in action" +></p> +<h2 id="tools">Tools +</h2><h3 id="uv"><a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a> +</h3><p>🚀 A single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +⚡️ 10-100x faster than pip.</p> +<ul> +<li>Provides comprehensive project management, with a universal lockfile.</li> +<li>Runs scripts, with support for inline dependency metadata.</li> +<li>Installs and manages Python versions.</li> +<li>Runs and installs tools published as Python packages.</li> +<li>Includes a pip-compatible interface for a performance boost with a familiar CLI.</li> +<li>Supports Cargo-style workspaces for scalable projects.</li> +<li>Disk-space efficient, with a global cache for dependency deduplication.</li> +<li>Installable without Rust or Python via curl or pip.</li> +<li>Supports macOS, Linux, and Windows.</li> +</ul> \ No newline at end of file diff --git a/cheatsheet/linux/index.html b/cheatsheet/linux/index.html new file mode 100644 index 0000000..9588a4b --- /dev/null +++ b/cheatsheet/linux/index.html @@ -0,0 +1,91 @@ +Linux Cheatsheet +
Featured image of post Linux Cheatsheet

Linux Cheatsheet

Tips and tricks for GNU/Linux and Unix

+

Black screen and LightDM doesn’t unlock

Add this to your /etc/lightdm/lightdm.conf file:

1
+2
+
[LightDM]
+logind-check-graphical=true
+

##Edit previous commands

fc is a shell builtin to list and edit previous commands in an editor. +In addition to editing a single line (which you can also do with C-x C-e), it also allows you to edit and run several lines at the same time. +You use it like this:

List previous commands

1
+2
+3
+4
+5
+
$ fc -l
+10259  nvim deploy.sh
+10260* cd ..
+10261* nvim content/cheatsheet/linux.md
+10262  cd
+

List commands with date (in zsh)

1
+2
+3
+4
+5
+
$ fc -ld
+10260* 19:38  cd ..
+10261* 19:38  nvim content/cheatsheet/linux.md
+10262  19:40  cd
+10263  19:40  fc -l
+

You can add the date too:

1
+2
+3
+4
+
$ fc -fld
+10262  1/10/2019 19:40  cd
+10263  1/10/2019 19:40  fc -l
+10264  1/10/2019 19:40  fc -ld
+

You can edit a range of commands

1
+
$ fc 10262 10264
+

The range can be relative to the current position, so the previous command is equivalent to:

1
+
$ fc -3 -1
+

If you save and exit, all commands are executed as a script, and it will be added to your history.

Source: https://shapeshed.com/unix-fc/

Prevent logoff from killing tmux sessions

Lately I’ve noticed that logging out of i3, intentionally or when i3 fails, would also kill any tmux or emacs sessions. +This is extremely annoying.

This is caused by a new default in logind (systemd’s login) to kill user process on logoff. +You can revert this setting in your logind.conf (/etc/systemd/logind.conf):

1
+
KillUserProcesses=no
+

Or only for a specific process (e.g., tmux):

1
+
systemd-run --scope --user tmux
+

Source: https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session

Upload a temporary file

Sometimes you just need to copy/paste a file from a server, and copying from the terminal can be a hassle. +These two services are command-line “pastebins” just one curl away:

1
+2
+3
+4
+5
+6
+7
+8
+
<command> | curl -F 'sprunge=<-' http://sprunge.us
+# OR
+
+<command> 2>&1 | curl -F 'f:1=<-' ix.io
+
+# OR
+
+<command> | curl -F"file=@-" https://ttm.sh
+

Install Fortinet SSLVPN support for NetworkManager

UPM (Universidad Politécnica de Madrid) uses a propriatary VPN solution. +The instructions for GNU/Linux on their website involve downloading a specific client (.tar.gz) and manually running it. +That works, but it is kind of a hassle. +A much more convenient alternative is installing this NetworkManager plugin:

1
+2
+
pacman -Sy networkmanager-fortisslvpn 
+# Or apt get install networkmanager-fortisslvpn 
+

Now you can simply add a new VPN connection in NetworkManager and manage it as you would any other connection.

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/cheatsheet/page/1/index.html b/cheatsheet/page/1/index.html new file mode 100644 index 0000000..5872863 --- /dev/null +++ b/cheatsheet/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/cheatsheet/ + \ No newline at end of file diff --git a/cheatsheet/python/index.html b/cheatsheet/python/index.html new file mode 100644 index 0000000..ef2324e --- /dev/null +++ b/cheatsheet/python/index.html @@ -0,0 +1,24 @@ +Python +
Featured image of post Python

Python

Tips and useful libraries for python developers

+

Interesting libraries

TQDM

From tqdm’s github repository:

tqdm means “progress” in Arabic (taqadum, تقدّم) and an abbreviation for “I love you so much” in Spanish (te quiero demasiado).

TQDM in action

Tools

uv

🚀 A single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +⚡️ 10-100x faster than pip.

  • Provides comprehensive project management, with a universal lockfile.
  • Runs scripts, with support for inline dependency metadata.
  • Installs and manages Python versions.
  • Runs and installs tools published as Python packages.
  • Includes a pip-compatible interface for a performance boost with a familiar CLI.
  • Supports Cargo-style workspaces for scalable projects.
  • Disk-space efficient, with a global cache for dependency deduplication.
  • Installable without Rust or Python via curl or pip.
  • Supports macOS, Linux, and Windows.
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/favicon.png b/favicon.png new file mode 100644 index 0000000..e53125d Binary files /dev/null and b/favicon.png differ diff --git a/img/2014/04/pic02.jpg b/img/2014/04/pic02.jpg new file mode 100644 index 0000000..385722d Binary files /dev/null and b/img/2014/04/pic02.jpg differ diff --git a/img/2014/04/pic03.jpg b/img/2014/04/pic03.jpg new file mode 100644 index 0000000..692fb88 Binary files /dev/null and b/img/2014/04/pic03.jpg differ diff --git a/img/2014/09/pic01.jpg b/img/2014/09/pic01.jpg new file mode 100644 index 0000000..3bd5296 Binary files /dev/null and b/img/2014/09/pic01.jpg differ diff --git a/img/emacs.png b/img/emacs.png new file mode 100644 index 0000000..4c7cd3b Binary files /dev/null and b/img/emacs.png differ diff --git a/img/linux.png b/img/linux.png new file mode 100644 index 0000000..199163c Binary files /dev/null and b/img/linux.png differ diff --git a/img/logo-origin.png b/img/logo-origin.png new file mode 100644 index 0000000..bd2017c Binary files /dev/null and b/img/logo-origin.png differ diff --git a/img/logo.jpeg b/img/logo.jpeg new file mode 100644 index 0000000..21e225f Binary files /dev/null and b/img/logo.jpeg differ diff --git a/img/main/logo.jpg b/img/main/logo.jpg new file mode 100644 index 0000000..3098370 Binary files /dev/null and b/img/main/logo.jpg differ diff --git a/img/me-bat.png b/img/me-bat.png new file mode 100644 index 0000000..bd2017c Binary files /dev/null and b/img/me-bat.png differ diff --git a/img/me.png b/img/me.png new file mode 100644 index 0000000..691023e Binary files /dev/null and b/img/me.png differ diff --git a/img/me_hu_57f477f2a0e68f7e.png b/img/me_hu_57f477f2a0e68f7e.png new file mode 100644 index 0000000..c4700e6 Binary files /dev/null and b/img/me_hu_57f477f2a0e68f7e.png differ diff --git a/img/python.png b/img/python.png new file mode 100644 index 0000000..3b63931 Binary files /dev/null and b/img/python.png differ diff --git a/img/rpi.png b/img/rpi.png new file mode 100644 index 0000000..58bd721 Binary files /dev/null and b/img/rpi.png differ diff --git a/img/uv.png b/img/uv.png new file mode 100644 index 0000000..bca946e Binary files /dev/null and b/img/uv.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..070d3de --- /dev/null +++ b/index.html @@ -0,0 +1,46 @@ +J. Fernando Sánchez +
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/index.xml b/index.xml new file mode 100644 index 0000000..fc68b52 --- /dev/null +++ b/index.xml @@ -0,0 +1,2046 @@ +J. Fernando Sánchezhttps://balkian.com/Recent content on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 07 Mar 2025 10:24:52 +0100RDF Is Deadhttps://balkian.com/p/rdf-is-dead/Fri, 07 Mar 2025 10:24:52 +0100https://balkian.com/p/rdf-is-dead/<p>A big part of my research has been around vocabularies and semantic annotation. +And, to be honest, I&rsquo;ve grown increasingly dissatisfied with the field. +To the point where I dread having to work on it. +Some day I will write about it in length, but today I&rsquo;ve stumbled upon a post that covers the topic quite well: <a class="link" href="https://terminusdb.com/blog/the-semantic-web-is-dead/" target="_blank" rel="noopener" +>The Semantic Web is Dead - <del>Long Live the Semantic Web</del></a> (styling mine).</p> +<p>In particular, this section has really resonated with me:</p> +<blockquote class="note"><h1 id="academics-and-industry">Academics and Industry +</h1><p>The political economy of academia and its interaction with industry is the origin of our current lack of a functional Semantic Web.</p> +<p>Academia is structured in a way that there is very little incentive for anyone to build usable software. Instead, you are elevated for rapidly throwing together an idea, a tiny proof of concept, and to iterate on microscopic variations of this thing to produce as many papers as possible.</p> +<p>In engineering, the devil is in the detail. You really need to get into the weeds before you can know what the right thing to do is. This is simultaneously a devastating situation for industry and academia. Nobody is going to wait around for a team of engineers to finish building a system to write about it in Academia. You’ll be passed immediately by legions of paper pushers. And in industry, you can’t just be mucking about with a system that you might have to throw away.</p></blockquote>Tips for efficient collaborationhttps://balkian.com/p/efficient-collaboration/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/p/efficient-collaboration/<h2 id="background">Background +</h2><blockquote class="note"><p>TL;DR I work in academia. This post focuses on advice I&rsquo;d give a younger me to be a more effective supervisor and project lead.</p></blockquote> +<p>My role in my research group has evolved from individual contributor to project lead that manages a team of multiple students. +This often involves coordinatating with other senior researchers and their teams.</p> +<p>This post is a collection of advice I would have given myself back when I started this journey. +It is also an excuse to reflect on these ideas I&rsquo;ve been implicitly applying everyday, and maybe learn a few things more in the process.</p> +<p>In my field, projects are often tied to a specific grant or some sort of public funding. +This means that the main concern of the lead is to ensure that the results at the end of the project match the description in the project proposal. +It also typically means maximizing the number of publications related to the project and their overall impact.</p> +<p>To do so, most projects rely on three types of staff: a) senior researchers (post-doc); b) junior researchers (PhD students); and c) interns doing their bachelor&rsquo;s or master&rsquo;s thesis. +The level of contribution is generally inversely propotional to the level of mastery of the contributor: PhD students design and develop the main contributions (both software and experimental) under the supervision of senior researchers (advisor/supervisor), and interns take care of tasks that are narrow in scope and not crucial to any academic contribution. +For instance, a PhD student may develop a new model for text classification, and an intern will wrap that model in an HTTP service with a nice UI. +When the service and UI part is intricate and has some potential academic merit, that task may be conducted by a PhD student as part of their thesis. +That was precisely the case with Senpy, which was part of my PhD thesis, and it has since been used by dozens of students to develop services in the context of other research projects.</p> +<h2 id="reasons-to-form-a-team">Reasons to form a team +</h2><p>In my opinion, a team has two advantages over a single contributor. +The first one is that collaboration often generates <strong>synergies</strong>, leading to surprising and enriching results (a team is greater than the sum of its parts) +Carefully selecting your team members and creating an environment that is conducing to these synergies is a topic on its own, and I will not get too deep into it here.</p> +<p>The second advantage can be summarized as <strong>concurrency</strong>: tasks can be tackled by more than one member. +This often implies some sort of parallelism. +Tasks tend to be split between different members, in hopes of speeding up the process. +But it can also be beneficial to have concurrency even in the absence of parallelism: different members can take turns solving the same problem. +This is common when the task requires exclusive access to a resource (e.g., performing an experiment on an expensive machine).</p> +<h2 id="challenges-of-teams-of-students">Challenges of teams (of students) +</h2><p>Just like in computer science, coordination in a concurrent project involves a non-negligible overhead. +The objective is to minimize this overhead.</p> +<p>I&rsquo;ve really struggled with managing teams in the past. +Given my context, I often attributed the failures to lack of time (having to juggle teaching and research), lack of training and preparation on the intern&rsquo;s side (they&rsquo;re often undergrads), lack of appeal or definition of the task (too academic-y), or any other external factors.</p> +<p>While all those aspects play an important role, some of them are out of our control as a project lead in academia: grants require a certain type of project and workflow, and the quality of our interns is bounded by the quality of our degrees. +So I think it is more constructive to focus on things that we can control. +In other words: we have to play the hand we&rsquo;re given.</p> +<p>Besides, there is no merit in achieving good results with excellent students/engineers. +They would succeed on their own even if you weren&rsquo;t there. +The real test for a good leader is succeeding with a subpar team.</p> +<p>In that vein, I&rsquo;ve reflected on my mistakes as a leader, and the inefficiencies of the teams around me. +I&rsquo;d classify my failures in the following areas:</p> +<ul> +<li>Delegation (and lack thereof). Piling up too many tasks and blocking progress.</li> +<li>Communication. Not having a coherent view of the state of affairs, the details of specific tasks, the general processes to follow, or the priorities of different tasks within a project.</li> +<li>Direction (or purpose). Not having a common direction</li> +</ul> +<h3 id="delegation">Delegation +</h3><p>I have a tendency to become a bottleneck in any project I am involved in: many tasks end up depending on me, either directly or indirectly. In the concurrency metaphor, I become a lock for many tasks, and a single executor for the rest. +This issue was not that apparent early on, when most of my work was as an individual contributor or I had the bandwidth to supervise and complete my tasks.</p> +<p>I think it is quite common to feel like delegating a task in these scenarios means:</p> +<ol> +<li>Defining the task in advance</li> +<li>Choosing an assignee for the task</li> +<li>Setting a deadline for the task</li> +<li>Explaining the task and the relevant context</li> +<li>Replying to multiple questions by e-mail or in person. Extra points when the questions make you wonder if any part of the explanation was ever clear.</li> +<li>Reviewing the results after the deadline</li> +<li>Realizing the assignee misunderstood the task or delivered something not even close to what you agreed upon</li> +<li>Going back to point 3.</li> +<li>When you&rsquo;re unlucky or short on time: giving up and doing the task yourself</li> +</ol> +<p>Many times, if felt like delegating tasks only lead to frustration and wasted time. +Especially when compared to the alternative:</p> +<ul> +<li>Defining the task</li> +<li>Setting a deadline</li> +<li>Finishing the task</li> +<li>Profit</li> +</ul> +<p>Luckily, some students and projects were an exception to this. +They worked autonomously and delivered something beyond the minimum requirements. +This reinforced my helplessness and the feeling that the problem was not being able to work with experienced engineers.</p> +<p>However, I now believe that the truth lies somewhere in between. +Sometimes your circumstances make it quite hard or inefficient to delegate tasks. +And some times may not be good candidates for delegation. +But most of the times you can take advantage of having an extra pair of hands, you just have to do that effectively.</p> +<h3 id="communication">Communication +</h3><p>Small teams rely on implicit knowledge more than they realize. +Even more so if the team is made up of highly specialized people that have worked in the same environment with mostly the same people for years.</p> +<p>Communication is a broad term. +It includes technical and concrete things such as how a certain task should be done. +But it also includes broader things like etiquette, organizational values, and who is more willing to help you out on certain topics on a Friday afternoon.</p> +<p>Here, I would take a page out of Python&rsquo;s zen and recommend that &ldquo;explicit is better than implicit&rdquo;. +Implicit (or tacit) knowledge comes with a whole set of drawbacks:</p> +<ul> +<li>It makes onboarding new users harder. Without a common knowledge base, all knowledge transference has to rely on personal interactions. Even worse, those interactions are probably organized on the spot, and are likely to miss important points.</li> +<li>It makes you heavily reliant on your current members (and their memory).</li> +<li>It impedes proper evaluationn and progress, since they are not written anywhere.</li> +<li>It increases the likelihood of misunderstandings when two members have conflicting beliefs, and makes it harder to detect them until it is too late.</li> +<li>It makes contradictions and (unknowingly) changing your mind much more likely. It can happen to the best of us, especially if you are involved in too many projects. When contraditions happen often, your colleagues will learn not to rely on your opinion.</li> +</ul> +<p>On the other hand, communication has to go both ways. +This means that your newer members need to be able to communicate when something is going wrong or can be improved (backpressure). +They should also feel free to talk about their motivation, state of mind, and feelings, <strong>when appropriate</strong>. +That last part is quite subjective, of course. +Try to find your - and your organization&rsquo;s - middle ground between &ldquo;I don&rsquo;t care how you feel, just do your job&rdquo; and &ldquo;sure, you can go to the Maldives on short notice. Oh, and don&rsquo;t worry about not having met a deadline in months, I&rsquo;m sure you&rsquo;re stressed and can use some vacation but will work remotely if we need you&rdquo;.</p> +<p>I personally feel a sweet spot is treating your coworkers like people, being empathetic and compassionate. +Part of being a good coworker is fulfilling the duties and obligations you accepted when signing your contract. +First and foremost, because not fulfilling them means someone else will have to work harder to make up for it. +And, secondly, because doing our part is the only way to move the organization (and research) forward.</p> +<h3 id="direction">Direction +</h3><p>By failure in direction I mean not keeping a consistent and shared set of general goals, principles and values in your organization. +In order to really take part in any enterprise, you need to have a clear understanding of the objectives and motivation. +When it comes to specific tasks, <em>what you&rsquo;re doing</em> is often not as important as the <em>why you&rsquo;re doing it</em>. +In fact, there may be times where you aren&rsquo;t truly sure of what exactly it is that you are doing, but you trust the process and the motivation behind the task.</p> +<p>I&rsquo;ve seen two failure modes in this regard. +The first one is to not have a clear direction. +The end result is that members of the team are not really that committed. +If no other <em>why</em> is provided, we are only left with <em>because they pay me to do it</em>. +And academia is not known to pay particularly well, to be honest.</p> +<p>The other mode is to provide contradicting or incompatible directions. +This can be in a short period of time, leading to the impression that there isn&rsquo;t really any conviction in the message. +But it can also be done over a longer period of time. +That can be perfectly acceptable, provided that the change in direction is justified and compatible with the principles of the organization.</p> +<p>Failure in direction is somewhat related to communication, but it is subtly different. +An organization can excel at communication, but change their direction constantly. +Arguably, a thorough communication strategy makes radical changes in direction less likely. +On the one hand, a change in direction needs to be documented, which can be a pain. +On the other hand, a written change is easier to spot and more likely to generate complaints.</p> +<h2 id="rules">Rules +</h2><p>I&rsquo;d argue that the path to successfully managing a research team lies in roughly the following key goals:</p> +<ul> +<li>Fostering autonomy</li> +<li>Avoiding miscommunication</li> +<li>Optimizing your contribution</li> +</ul> +<p>The remaining of the post will be a series of tasks or rules to achieve these goals.</p> +<blockquote class="warning"><p>Most of these ideas probably generalize well to collaboration outside of academia, but I hesitate to make more general claims.</p></blockquote> +<h3 id="fostering-automony">Fostering automony +</h3><p>The tips here are aimed at avoiding supervision overhead and training future leads.</p> +<h4 id="provide-a-simplified-version-of-the-bigger-picture">Provide a (simplified version of the) bigger picture +</h4><p>Try to paint the bigger picture, even for menial tasks within large projects. +For you, this may be the nth project you&rsquo;re involved in this year, but the new intern may not have even heard about European projects before. +Going back to the idea of direction, it is easier to work on something if you know the context of your work.</p> +<p>Having a general idea of the project and the context of your task will also help you make decisions on your own. +For instance, if I am told to develop a shiny new API for text classification, I may have to ask many questions: 1) what will be input look like?; 2) what should the parameters be?; 3) am I using POST or GET requests?; 4) should I return a JSON object or an XML?&hellip; +What if, instead of that, I am also told this API will be used in the context of project X, that our organization will be the only consumers of the API, and they also give me a link to the project&rsquo;s docs. +I may be able to figure out some of those answers on my own (e.g., by finding examples in the project&rsquo;s website), or decide that some questions are not vital at this point (e.g., if we are the only consumers, we can change from GET to POST if we need to much more easily).</p> +<p>One caveat here is that a link to the documentation or some vague words about the project do not constitute proper context. +You are responsible for summarizing the important bits of the context, providing instructions on how to navigate the reference materials, and being open to answer questions that may arise in the exchange.</p> +<h4 id="do-not-discuss-implementation-details-unless-strictly-necessary">Do not discuss implementation details unless strictly necessary +</h4><p>There is a fine line between discussing a non-trivial implementation detail and bikeshedding for hours about class names and code best practices. +For that reason, you should try to prioritize discussing high-level parts of the problem and the assignment, and trust the student to figure out the details on their own, or come back to you for clarification.</p> +<p>It is very common that students focus on very specific details when they are sharing their progress with their supervisors. +They will generally try to start by showing snippets of code and their results. +I find it helpful to remind them to explain their problems top-to-bottom, starting with a sentence or two about the context of their project, the description and motivation of the specific task they were doing, and the relationship with previous (and future) tasks. +That usually helps figure out the level of understanding of the student, whether there are any conceptual errors, and whether the specific block or problem is really worth discussing during the meeting.</p> +<p>Some technical problems will warrant a discussion in detail, either due to their complexity or their importance to the project. +In those cases, always limit the time you will spend on that specific issue ahead of time, and make sure to allow for some time at the end of the meeting to go back to any important high-level details.</p> +<p>If there are other students that worked on similar projects, do not hesitate to refer your new student to them. +It can be an opportunity for them to collaborate, and for the original student to work on explaining and teaching technical issues.</p> +<h4 id="provide-feedback">Provide feedback +</h4><p>Make a point of evaluating the results of each student on every level, and provide constructive and actionable feedback to them. +Even if no technical issues arise during the project, try to review the code and give some tips (e.g., formatting, code structure, DRY). +Try to focus on bigger issues and enforcing best practices before nitpicking and giving feedback on small subjective improvements.</p> +<p>Make it clear when your feedback is objective/best practice (e.g., a function is deprecated) and when it is a matter of preference. +If it is the latter, try to provide more than one alternative, to encourage them to think about it and make an educated decision.</p> +<h4 id="take-documentation-and-knowledge-transfer-seriously">Take documentation and knowledge transfer seriously +</h4><p>Taking the time to write down basic documentation can save a lot of time in the long run. +Besides, most of the job of mentoring a new student is lost when that student finishes their degree and leaves to find a job in industry. +Good documentation can remain in your organization and be extended long after the intern is gone.</p> +<p>This is very obvious for specific tools, whether internal or public. +Good documentation means any new member can check the tool and use it without much assistance. +Even better documentation helps newcomers contribute to the tool. +Make sure to make it clear who to approach if something is missing from the documentation, and make it easier to do so than to make assumptions and use the tool incorrectly.</p> +<p>Writing documentation can be very time consuming, and sometimes it is hard to know exactly what things to focus on when writing the docs. +You need to anticipate the needs of the future user. +If you are short on time, a good strategy is to delegate the writing of the documentation. +Instead of going into details, you can write a very barebones version and training a new user to use and contribute to your tool. +Then, leave it up to the new user to extend the documentation, including more details and pitfalls. +As a bonus, reading and fixing the docs will give you a better sense of how well that new user understands the tool, as well as possible improvements.</p> +<p>This tip also applies to more general areas such as machine learning, graph neural networks, or simulation. +Just remember you do not need to reinvent the wheel in those cases. +A simple summary and a list of references to expand on the topic could be more than enough. +Make sure to also include any specifics that apply to your organization. +For instance, point to repositories on github (public or private) that can be used to explore the topic, examples of similar projects in the domain, etc.</p> +<p>Identify what information is important for any new hire and present it to them as clearly as possible. +Part of that information should be where and how common knowledge is stored and shared, should they need more information in the future. +Make this documentation as easy to discover and consume as possible. +Centralizing this common information in the form of a wiki is often a good idea.</p> +<p>Lastly, make it easy for any member of your organization to update this common documentation, and encourage them to do so. +Whenever a member asks you something useful that is not documented, don&rsquo;t just answer the question. +Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer. +If your organization&rsquo;s culture does not encourage using these docs, they will quickly get outdated and fall out of use.</p> +<p>One example of taking this documentation approach really seriously is <a class="link" href="https://oxide.computer/" target="_blank" rel="noopener" +>Oxide (computer company)</a>. +They have a process they call <a class="link" href="https://rfd.shared.oxide.computer/" target="_blank" rel="noopener" +>Request For Discussion (RFD)</a>, which they use to discuss and document both technical and organizational decisions. +For instance, they have <a class="link" href="https://rfd.shared.oxide.computer/rfd/0537" target="_blank" rel="noopener" +>RFDs on why they record every meeting</a>, <a class="link" href="https://rfd.shared.oxide.computer/rfd/0110" target="_blank" rel="noopener" +>RFDs about their choice of database</a>, and even <a class="link" href="https://rfd.shared.oxide.computer/rfd/0001" target="_blank" rel="noopener" +>an meta-RFD that discusses the motivation RFDs and how the process should work</a>.</p> +<h4 id="trust-your-teammates-ability-to-learn">Trust your teammate&rsquo;s ability to learn +</h4><p>I&rsquo;ve been bitten by this way too many times. +Your students are probably more capable of learning than you think, especially if you have set up your documentation right. +What they lack in experience, they make up for with free time, a (more) neuroplasticity and determination.</p> +<p>Sure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.</p> +<h4 id="use-tools-wisely">Use tools wisely +</h4><p>Your students probably have little experience with code versioning, reviewing processes, time management, etc. +A good choice of tools and some training can go a long way and make your life much easier in the long run. +It will also give your students a taste of what working in a bigger/real company feels like and a head start.</p> +<p>For instance, using git makes it easier to collaborate on code. +It also ensures that your results will not be lost if your student&rsquo;s laptop gets stolen.</p> +<p>Using GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students. +It will force them to commit working code, and it will make it easier to check their results and discuss the end result.</p> +<p>Using overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results. +You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration. +In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.</p> +<p>Also, on a related note, make sure every team member has a proper development setup. +It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work. +It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.</p> +<h4 id="encourage-cooperation">Encourage cooperation +</h4><p>Do not become the center of every conversation. +If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.</p> +<p>The ability to discuss with your peers and report only when needed will be extremely important for them in the future. +They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are). +That might lead to valuable insights and improvements for your team and project.</p> +<p>Moreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.</p> +<h4 id="reward-proactivity">Reward proactivity +</h4><p>The whole point of this section is to get your team to work independently when possible. +Be explicit about this goal to make sure it is clear to everyone. +And encourage behavior that aligns with this goal, even on a small scale.</p> +<p>For instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements. +Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time. +Do not jump straight to criticize it. +Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.</p> +<h4 id="dont-be-a-perfectionist">Don&rsquo;t be a perfectionist +</h4><p>Perfect is the enemy of done. +It is also the enemy of a happy co-worker.</p> +<p>Try to remember that you are dealing with students, and you were probably no better at their age. +Besides, you probably delegated the task beause you did not have any spare time to do it yourself. +<code>FIXME</code> is often better than <code>TODO</code>.</p> +<p>Take the opportunity to provide some feedback and teach them something useful. +Some mistakes are also worth adding to your documentation, or presenting to other students in a presentation.</p> +<h3 id="avoiding-miscommunication">Avoiding miscommunication +</h3><p>A common source of wasted effort and unnecessary back-and-forth is miscommunication. +These are some tips to help keep everyone on the team informed and aligned.</p> +<h4 id="make-priorities-clear">Make priorities clear +</h4><p>All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks. +This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.</p> +<h4 id="define-boundaries-and-abstractions">Define boundaries (and abstractions) +</h4><p>Once again, the goal is generally to achieve some sort of parallelism between your team members. +In order to do that, they need to know how they will interact with each other.</p> +<p>On a more general level, this means knowing the responsibilities and scope of your work.</p> +<p>On a more specific level, it means knowing their dependency graph. +In other words, whether the progress of one team member will depend on the results of another one. +Whenever there is a dependency, the interface should be made very clear. +This often takes the form of an API, a file with a given format, or a section of a document.</p> +<p>Take some time to define the boundary as precisely as needed at that point in the project. +I would suggest having specific examples that you can discuss and modify. +It is hard to discuss in the abstract, especially for inexperienced contributors. +When in doubt, default to the simplest option (e.g., a common file vs using a database). +Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there. +Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.</p> +<p>One type of failure I&rsquo;ve seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples. +That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.</p> +<h4 id="be-approachable">Be approachable +</h4><p>Did I wrote a whole section about autonomy? Yes. +Is the end goal to do more and talk less? Also yes. +Thing is, no process is perfect, and misunderstandings are bound to happen at some point. +If your only response to questions is a grumpy face or a &ldquo;read the freaking docs&rdquo;, your students will not alert you when something really needs your attention, and you will find out too late. +For instance, the documentation may be unclear, or your processes may be inadvertedly alienating new members and making new hires harder.</p> +<p>Another way to be approachable is to be clear about your shortcomings, and whether something you are saying is negotiable and/or debatable. +My rule of thumb is to err on the side of negotiable, and only be strict when it is really necessary (e.g., time constraints or an unproductive student). +We are all more likely to finish our tasks if we feel them ours, if we a say in how and when to perform them.</p> +<p>Just to be clear, approachable does not mean you have to be their confident or their best friend. +It also does not mean that it is okay to challenge or question you continuously. +Some times it is okay to simply say &ldquo;just do as I say&rdquo;.</p> +<h4 id="review-frequently">Review frequently +</h4><p>One type of review is individual. +It involves reviewing code on github, or reading deliverables and papers on overleaf. +It can help catch misunderstandings, and measure the true rate of progress in the individual tasks. +The other type of review is done as a group, by going through the key progress and action points. +This type of review helps everyone stay on the same page, and catch any general drifts in the project, such as misaligned priorities.</p> +<p>The frequency of each type of review depends on the specific nature of the project, the types of tasks being performed by the student, and your confidence on the student&rsquo;s abilities.</p> +<h3 id="optimizing-your-contribution">Optimizing your contribution +</h3><p>Tips on optimizing your contribution to the team.</p> +<h4 id="prioritize-prioritize-prioritize">Prioritize, prioritize, prioritize +</h4><p>Part of your job as a project lead is to identify the main goals in a project and to prioritize the tasks that will lead you there. +On the other hand, you are part of a research group, and you should be actively involved in its health and future. +Lastly, you are also in charge of the life-long project that is your research career.</p> +<p>In all these cases, your goal should be to identify the long term goals, come up with a sound strategy, and prioritize the tasks that will lead you and your group there. +Keeping your priorities straight will help you make steady progress, and avoid bikeshedding and changing goalposts. +It will also help you steer your progress in the right direction, since we all have limited time and effort and can&rsquo;t do everything at once.</p> +<p>The fact that your time is limited also means that you will need to decide how to prioritize these three roles. +I&rsquo;ve listed them in increasing level of importance for me. +It means that it is okay to focus on a specific project for a while, but if progress in your career is stalled - usually through publication - you need to reevaluate and concentrate your efforts on that.</p> +<h4 id="be-okay-with-short-term-inefficiencies">Be okay with (short-term) inefficiencies +</h4><p>I&rsquo;ve personally struggled with delegating tasks that will take me orders of magnitude less work than they will a student. +Thing is, most tasks will fall under this category, and your time is limited, so you have to delegate if you want to have time for more important matters. +If you never delegate any tasks, you are not allowing your team to learn and catch up on whatever technical skills are required. +Besides, you are not improving yourself on the managerial side of things. +It turns out delegating is hard, it requires a whole set of non-technical skills. +I suspect this is oftentimes the reason we don&rsquo;t do delegate in the first place: delegating is hard, and technical tasks are usually more straightforward, so we just don&rsquo;t want to do the work.</p> +<h4 id="dont-neglect-training">Don&rsquo;t neglect training +</h4><p>You are a senior researcher. +You probably know how to solve problems in your domain quite efficiently. +In my case, that means processing data and developing code.</p> +<p>That means I could dedicate my days to processing data and developing new code for my group. +That group would likely be used in multiple projects. +However, there is a hard limit to how much code I can push out in a day, especially if you take into account other obligations such as teaching.</p> +<p>A wiser strategy would be to set aside some of that coding time to instead help students become better programmers. +Firstly, because those students will be thankful and more motivated to work than when they are left to learn on their own without much guidance. +Secondly, because those students will then be more prepared to help me out if I delegate a task to them. +And, lastly, because these students have a whole life in fron of them. +A life full of big projects of their own, and contributions to society. +That little training time can have a compounding effect in the future.</p> +<h4 id="set-a-time-limit-for-your-interactions-in-advance">Set a time limit for your interactions in advance +</h4><p>Really long slots can easily lead to bikeshedding and going unnecessarily deep into implementation details, which is clearly an inefficient use of your time. +Even worse, our attention span and memory are finite, so longer and dense meetings can lead to fatigue and to missing or dilluting important points in the conversation.</p> +<p>For these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary. +You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.</p> +<h2 id="beyond-your-team">Beyond your team +</h2><p>The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control. +But teams rarely work in isolation, you will most likely +In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization&rsquo;s culture and sense of belonging.</p> +<p>Many of the aspects I talked about in the team section apply here. +For instance, the obsession with documentation can - and should - be applied organization-wise. +The same goes for defining boundaries and using concrete examples when collaborating with other teams. +For most intents and purposes, you can treat other teams as another contributor to your team. +Just one that will be more costly and slow to interact with.</p> +<p>If possible, I&rsquo;d try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved. +Avoid involving whole teams in discussions when the broad strokes have not been defined yet. +The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.</p> +<p>On the organization&rsquo;s side, I would suggest having an honest conversation about your core principles. +I really liked <a class="link" href="https://www.youtube.com/watch?v=9QMGAtxUlAchttps://www.youtube.com/watch?v=9QMGAtxUlAc" target="_blank" rel="noopener" +>Bryan Cantrill&rsquo;s talk about principles of technology leadership</a>. +He goes deep into the effects that principles have had on well known companies, and how to go about defining your company&rsquo;s principles. +I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.</p> +<p>More generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive. +And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier. +Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.</p>Bridging RDF, JSON-LD and Dataclasseshttps://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/<p>In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.</p> +<h2 id="the-rdflib-way">The <code>rdflib</code> way +</h2><p><code>rdflib</code> only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, <code>Entity</code>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">entry</span> <span class="o">=</span> <span class="n">Entry</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">entry</span><span class="p">[</span><span class="s1">&#39;vocab:property&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">jsonld</span><span class="p">())</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Would print something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@id&#34;</span><span class="p">:</span> <span class="s2">&#34;:Entry_202505....&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;prefix:Entity&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;vocab:property&#34;</span><span class="p">:</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the</p> +<p>Pros:</p> +<ul> +<li>Flexible/extensible</li> +<li>Lightweight. This is mostly JSON-LD in Python&rsquo;s clothing.</li> +<li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li> +</ul> +<p>Cons:</p> +<ul> +<li>Discoverability. Documentation and examples are needed to know which attributes to use</li> +<li>Error-prone. It is easy to misuse a property, or introduce typos</li> +<li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li> +</ul> +<h2 id="the-object-oriented-way">The object-oriented way +</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like <a class="link" href="https://pydantic.dev/" target="_blank" rel="noopener" +>pydantic</a> that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p> +<p>This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p> +<p>Pros:</p> +<ul> +<li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li> +<li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li> +</ul> +<p>Cons:</p> +<ul> +<li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li> +<li>Polymorphism.</li> +</ul> +<h2 id="a-hybrid-approach">A hybrid approach +</h2><p>Whichever solution is chosen in the end, it needs to:</p> +<ul> +<li>Make it easy and error-proof to add the most common types of annotations</li> +<li>Allow for additional annotations/attributes to be added</li> +<li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li> +<li>Allow for deserialization of custom types</li> +<li>Allow multiple consumers to add their own annotations</li> +</ul>uv - One rust tool to rule all pythonshttps://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/Mon, 17 Feb 2025 23:02:47 +0100https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/<img src="https://balkian.com/img/uv.png" alt="Featured image of post uv - One rust tool to rule all pythons" /><p>Long story short: I&rsquo;m now using <a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a>, and so should you. +It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.</p> +<h2 id="context">Context +</h2><p>For years, my strategy to manage python projects has been a mix of a custom <code>setup.py</code>, several hand-crafted <code>requirements.txt</code> files (through <code>pip freeze</code>), a custom virtualenv per project, and multiple tools to upload to PyPI. +Although this works, this setup has many drawbacks:</p> +<ul> +<li>It requires user intervention (creating a venv, sourcing it, handling new deps). This isn&rsquo;t ideal if you want new (probably inexperienced) users to use your projects.</li> +<li>On a similar note, the whole process needs to be well documented if you want other users to contribute or maintain the code.</li> +<li>Pinning dependency versions is finicky, and I&rsquo;ve run into problems beause of that.</li> +<li>Creating a new project involves a template, or copying files from an older project.</li> +</ul> +<p>Of course, this is nothing new. +There is a whole site dedicated to <a class="link" href="https://packaging.python.org/en/latest/" target="_blank" rel="noopener" +>packaging your Python project</a>. +A plethora of different projects have come and go, with varying degrees of success.</p> +<h2 id="alternatives-poetry">Alternatives (poetry) +</h2><p>About a year before trying <code>uv</code>, I tried to catch up with the ecosystem and get to know the <code>blessed new way</code>. +However, the task proved to be a little more difficult, as the landscape is filled with a myriad of alternatives, each with their own set of drawbacks and detractors. +Packaging has historically been a weak spot, in ironical contradiction to the Zen of Python&rsquo;s &ldquo;There should be one&ndash; and preferably only one &ndash;obvious way to do it&rdquo;,</p> +<p>I eventually settled on <a class="link" href="https://python-poetry.org/" target="_blank" rel="noopener" +>poetry</a>. +Mostly because it seemed like the most popular alternative.</p> +<p>There are many things I liked about it. +First of all, having a convention for dependencies (<code>pyproject.toml</code>) and a tool that properly handles them was nice. +It also removed the need to remember specific incantations to build and publish my Python projects. +Lastly, I mixed it <code>poetry2nix</code> to create reproducible python environments using nix. +This makes for a very powerful experience.</p> +<p>However, there were multiple hiccups. +First of all, it took me some time to figure out which specific fields to use (each tool can define ad-hoc properties in a the <code>pyproject.toml</code> file), and some of them seemed redundant with the more generic ones. +Full disclosure, this specific point might be a mistake on my side, and I do not remember the details. +The second one is speed. +(Re-)creating an environment took a non-negligible amount of time.</p> +<h2 id="enter-light-uv">Enter <del>light</del> <code>uv</code> +</h2><p>According to its repository, <code>uv </code>can replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +Not only that, but it also claims to do that 10-100 times faster than pip. +I must admit that it being written in rust was a another selling point for me, as I&rsquo;m looking for excuses to collaborate in a decently-sized rust projejct.</p> +<p>Installing it is dead simple: simply download the binary (e.g., with curl) or run <code>pip install uv</code>. +You won&rsquo;t need much more: <code>uv</code> seems to just do the right thing out of the box. +And it does it really, really fast. +The rest of the time it gets out of the way.</p> +<p>My only gripe so far is that I don&rsquo;t seem to find a built-in command to drop into a shell, but that is nothing that <code>uv run $SHELL</code> cannot fix.</p> +<h2 id="common-operations">Common operations +</h2><h3 id="initialize-a-repository">Initialize a repository +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv init +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="adding-dependencies">Adding dependencies +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv add senpy +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="running-commands-inside-the-environment">Running commands inside the environment +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv run &lt;COMMAND&gt; +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"># e.g., run a shell using your python version and dependencies +</span></span><span class="line"><span class="cl">uv run $SHELL +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="dependency-tree">Dependency tree +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv shell +</span></span><span class="line"><span class="cl">Resolved 44 packages in 1ms +</span></span><span class="line"><span class="cl">my-project v0.1.0 +</span></span><span class="line"><span class="cl">├── fastapi[standard] v0.115.8 +</span></span><span class="line"><span class="cl">│ ├── pydantic v2.10.6 +</span></span><span class="line"><span class="cl">│ │ ├── annotated-types v0.7.0 +</span></span><span class="line"><span class="cl">│ │ ├── pydantic-core v2.27.2 +</span></span><span class="line"><span class="cl">│ │ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── starlette v0.45.3 +</span></span><span class="line"><span class="cl">│ │ └── anyio v4.8.0 +</span></span><span class="line"><span class="cl">│ │ ├── exceptiongroup v1.2.2 +</span></span><span class="line"><span class="cl">│ │ ├── idna v3.10 +</span></span><span class="line"><span class="cl">│ │ ├── sniffio v1.3.1 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── email-validator v2.2.0 (extra: standard) +</span></span><span class="line"><span class="cl">│ │ ├── dnspython v2.7.0 +</span></span><span class="line"><span class="cl">... +</span></span></code></pre></td></tr></table> +</div> +</div>Nix Recipe for Python Projectshttps://balkian.com/p/nix-recipe-for-python-projects/Mon, 13 Nov 2023 18:21:46 +0100https://balkian.com/p/nix-recipe-for-python-projects/<p>This is a quick and easy recipe to add a <code>default.nix</code> to any Python project with a <code>requirements.txt</code> file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="k">with</span> <span class="kn">import</span> <span class="sr">&lt;nixpkgs&gt;</span> <span class="p">{</span> <span class="p">};</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">let</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span> <span class="o">=</span> <span class="n">python311Packages</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="k">rec</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;impurePythonEnv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">venvDir</span> <span class="o">=</span> <span class="s2">&#34;./.venv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">buildInputs</span> <span class="o">=</span> <span class="p">[</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># A python interpreter including the &#39;venv&#39; module is required to bootstrap</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the environment.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">python</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># This execute some shell code to initialize a venv in $venvDir before</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># dropping into the shell</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">venvShellHook</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Those are dependencies that we would like to use from nixpkgs, which will</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># add them to PYTHONPATH and thus make them accessible from within the venv.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">numpy</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">requests</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># In this particular example, in order to compile any binary extensions they may</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># require, the python modules listed in the hypothetical requirements.txt need</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the following packages to be installed locally:</span> +</span></span><span class="line"><span class="cl"> <span class="n">taglib</span> +</span></span><span class="line"><span class="cl"> <span class="n">openssl</span> +</span></span><span class="line"><span class="cl"> <span class="n">git</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxml2</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxslt</span> +</span></span><span class="line"><span class="cl"> <span class="n">libzip</span> +</span></span><span class="line"><span class="cl"> <span class="n">zlib</span> +</span></span><span class="line"><span class="cl"> <span class="p">];</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Now we can execute any commands within the virtual environment.</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># This is optional and can be left out to run pip manually.</span> +</span></span><span class="line"><span class="cl"> <span class="n">postShellHook</span> <span class="o">=</span> <span class="s1">&#39;&#39; +</span></span></span><span class="line"><span class="cl"><span class="s1"> pip install -r requirements.txt +</span></span></span><span class="line"><span class="cl"><span class="s1"> &#39;&#39;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now, you will get a clean environment by running:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">nix-shell +</span></span></code></pre></td></tr></table> +</div> +</div>Kanata: advanced keyboard configurationhttps://balkian.com/p/kanata-advanced-keyboard-configuration/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/p/kanata-advanced-keyboard-configuration/<p><a class="link" href="https://github.com/jtroo/kanata/" target="_blank" rel="noopener" +>Kanata</a> is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you&rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.</p> +<p>The project was inspired by the more popular <a class="link" href="https://github.com/kmonad/kmonad" target="_blank" rel="noopener" +>KMonad</a>, and the author cites some of the <a class="link" href="https://github.com/jtroo/kanata/blob/main/docs/kmonad_comparison.md" target="_blank" rel="noopener" +>differences</a>. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +<a class="link" href="https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd" target="_blank" rel="noopener" +>Here&rsquo;s a very complete config that serves as documentation</a>.</p> +<p>One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.</p> +<p><a class="link" href="https://github.com/rvaiya/keyd/" target="_blank" rel="noopener" +>Keyd</a> is another alternative with a more declarative configuration format, which might lend itself to smaller.</p> +<p>For now I&rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren&rsquo;t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that&rsquo;s the idea. +We&rsquo;ll see if I like it enough to stick with it.</p> +<p>For now, here&rsquo;s my very simple config:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span><span class="lnt">41 +</span><span class="lnt">42 +</span><span class="lnt">43 +</span><span class="lnt">44 +</span><span class="lnt">45 +</span><span class="lnt">46 +</span><span class="lnt">47 +</span><span class="lnt">48 +</span><span class="lnt">49 +</span><span class="lnt">50 +</span><span class="lnt">51 +</span><span class="lnt">52 +</span><span class="lnt">53 +</span><span class="lnt">54 +</span><span class="lnt">55 +</span><span class="lnt">56 +</span><span class="lnt">57 +</span><span class="lnt">58 +</span><span class="lnt">59 +</span><span class="lnt">60 +</span><span class="lnt">61 +</span><span class="lnt">62 +</span><span class="lnt">63 +</span><span class="lnt">64 +</span><span class="lnt">65 +</span><span class="lnt">66 +</span><span class="lnt">67 +</span><span class="lnt">68 +</span><span class="lnt">69 +</span><span class="lnt">70 +</span><span class="lnt">71 +</span><span class="lnt">72 +</span><span class="lnt">73 +</span><span class="lnt">74 +</span><span class="lnt">75 +</span><span class="lnt">76 +</span><span class="lnt">77 +</span><span class="lnt">78 +</span><span class="lnt">79 +</span><span class="lnt">80 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defcfg</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Your keyboard device will likely differ from this.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">linux-dev</span> <span class="nv">/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Windows doesn&#39;t need any input/output configuration entries; however, there</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; must still be a defcfg entry. You can keep the linux-dev entry or delete</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; it and leave it empty.</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsrc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="nf">-</span> <span class="nf">=</span> <span class="nv">bspc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">e</span> <span class="nv">r</span> <span class="no">t</span> <span class="nv">y</span> <span class="nv">u</span> <span class="nv">i</span> <span class="nv">o</span> <span class="nv">p</span> <span class="nv">[</span> <span class="nv">]</span> +</span></span><span class="line"><span class="cl"> <span class="nv">caps</span> <span class="nv">a</span> <span class="nv">s</span> <span class="nv">d</span> <span class="nv">f</span> <span class="nv">g</span> <span class="nv">h</span> <span class="nv">j</span> <span class="nv">k</span> <span class="nv">l</span> <span class="c1">; &#39; ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">\ z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">v</span> <span class="nv">b</span> <span class="nv">n</span> <span class="nv">m</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctl</span> <span class="nv">lmet</span> <span class="nv">lalt</span> <span class="nv">spc</span> <span class="nv">ralt</span> <span class="nv">rmet</span> <span class="nv">rctl</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">qwerty</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@warrows</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@slsft</span> <span class="nv">@dlalt</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@jrmet</span> <span class="nv">@kralt</span> <span class="nv">@lrsft</span> <span class="nv">@</span><span class="c1">;rctrl _ _</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">arrows</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">left</span> <span class="nv">down</span> <span class="nv">up</span> <span class="nv">rght</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">colemak</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">f</span> <span class="nv">p</span> <span class="nv">b</span> <span class="nv">j</span> <span class="nv">l</span> <span class="nv">u</span> <span class="nv">y</span> <span class="c1">; [ ] </span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@rlsft</span> <span class="nv">@slalt</span> <span class="nv">@tlmet</span> <span class="nv">g</span> <span class="nv">m</span> <span class="nv">@nrmet</span> <span class="nv">@eralt</span> <span class="nv">@irsft</span> <span class="nv">@orctrl</span> <span class="o">&#39;</span> <span class="nv">ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">XX</span> <span class="nv">z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">d</span> <span class="nv">v</span> <span class="nv">k</span> <span class="nv">h</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">@smartspace</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">magic</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">@clmk</span> <span class="nv">@qwerty</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">tab</span> <span class="nv">A-tab</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">bspc</span> <span class="nv">esc</span> <span class="nv">_</span> <span class="nv">ret</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defalias</span> +</span></span><span class="line"><span class="cl"> <span class="nv">warrows</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">w</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">arrows</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">alctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">a</span> <span class="nv">lctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">dlalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">d</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">flmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">f</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">jrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">j</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">k</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lrsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">l</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;rctrl (tap-hold 200 200 ; rctrl)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">rlsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">r</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tlmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="no">t</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">nrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">n</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">eralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">e</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">irsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">i</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">orctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">o</span> <span class="nv">rctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">clmk</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">colemak</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">qwerty</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">qwerty</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">smartspace</span> <span class="p">(</span><span class="nv">tap-dance</span> <span class="mi">200</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="nv">spc</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">magic</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="p">(</span><span class="nv">one-shot</span> <span class="mi">300</span> <span class="nv">lalt</span><span class="p">)</span> <span class="nv">spc</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">a</span> +</span></span><span class="line"><span class="cl"> <span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Archiveshttps://balkian.com/archives/Sun, 06 Mar 2022 00:00:00 +0000https://balkian.com/archives/Logitech MB850 combi in linuxhttps://balkian.com/p/logitech-mb850-combi-in-linux/Sat, 30 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mb850-combi-in-linux/<p>As a follow-up to my last post, I&rsquo;ve decided to also configure my mk850 combo (k850 + m720 triathlon).</p> +<p>Some notes:</p> +<ul> +<li>The keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc)</li> +<li>The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I&rsquo;ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM.</li> +</ul> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">evdev:input:* </span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_70039</span><span class="o">=</span><span class="s">leftctrl # bind capslock to w </span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0005v046DpB015*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e0</span><span class="o">=</span><span class="s">f19 +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e2=unknown +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7002b=unknown</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the settings have been applied by running <code>evemu-describe</code>:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> sudo /sbin/evemu-describe /dev/input/event&lt;id of your device&gt; <span class="p">|</span> grep KEY_ +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MX Keys for Mac on Linuxhttps://balkian.com/p/logitech-mx-keys-for-mac-on-linux/Fri, 29 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mx-keys-for-mac-on-linux/<p>I recently got Logitech MX Keys for Mac keyboard at work. +The German version, to be more precise. +This version was three times cheaper than the Windows equivalent with either US or ES layout. +Since I touch type anyway, I thought it was a bargain.</p> +<p>As soon as I plugged it in, I realized there were some glaring issues with the keyboard. +First of all, the Meta/Super and Alt keys are reversed in this keyboard. +In the normal/full version of this keyboard, Logitech gives an option to choose between Mac, Windows and iOS host, and that changes the behavior of the keys. +In this version, tho, only iOS and Mac are available.</p> +<p>Besides that, there&rsquo;s the issue of the grave (tilde) and angle keys switched as well.</p> +<p>Switching these keys around would be very easy with Xorg, but Wayland once again complicates things&hellip;</p> +<p>These issues almost made me return the keyboard. +Luckily, tho, there is another option: configuring the keys one level lower than wayland (and X11), through hwdb.</p> +<p>Long story short, this will configure any Logitech keyboard with the same product id (0x4092) to use a saner configuration:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="c1">#File: /etc/udev/hwdb.d/90-logitech-keyboard.hwdb</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0003v046Dp4092*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e2</span><span class="o">=</span><span class="s">leftmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e3=leftalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70039=leftctrl +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70064=102nd +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70035=grave +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e7=rightalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e6=rightmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7006d=compose</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div>Linux on the Microsoft Surface Gohttps://balkian.com/p/linux-on-the-microsoft-surface-go/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/p/linux-on-the-microsoft-surface-go/<p>Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. +These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.</p> +<h2 id="installing-the-kernel">Installing the kernel +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --depth <span class="m">1</span> https://github.com/jakeday/linux-surface.git ~/linux-surface +</span></span><span class="line"><span class="cl">cp -a ~/linux-surface /media/&lt;your usb&gt; +</span></span></code></pre></td></tr></table> +</div> +</div><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -a /media/&lt;your usb&gt;/linux-surface ~/ +</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/linux-surface/ +</span></span><span class="line"><span class="cl">sudo sh setup.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="booting-ubuntu-first">Booting ubuntu first +</h2><p>Switch out of Windows S mode.</p> +<p>Boot into the &ldquo;Command Prompt&rdquo;.</p> +<p>From Windows go to &ldquo;change advanced startup options&rdquo; and select &ldquo;restart now&rdquo;.</p> +<p>When it reboots, choose the &ldquo;Troubleshoot&rdquo; option, then choose the &ldquo;Advanced options&rdquo; option, and finally choose the &ldquo;Command Prompt&rdquo; option.</p> +<p>After the device reboots, login to the command prompt and then you should see a terminal with X:\windows\system32&gt;</p> +<p>At the prompt, check your UEFI entries:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /enum firmware +</span></span></code></pre></td></tr></table> +</div> +</div><p>Copy UEFI entry of &ldquo;Windows Boot Manager&rdquo; to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d &ldquo;Ubuntu&rdquo;</p> +<p>Copy the printed GUID number including the braces {} using Ctrl+C</p> +<p>Set file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \EFI\ubuntu\grubx64.efi</p> +<p>Set Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /set <span class="o">{</span>fwbootmgr<span class="o">}</span> displayorder <span class="o">{</span>guid<span class="o">}</span> /addfirst +</span></span></code></pre></td></tr></table> +</div> +</div><p>Check your UEFI entries again: bcdedit /enum firmware You should see something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">Firmware Boot Manager +</span></span><span class="line"><span class="cl">--------------------- +</span></span><span class="line"><span class="cl">identifier <span class="o">{</span>fwbootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl">displayorder <span class="o">{</span>3510232e-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>bootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>2148799b-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a67-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a68-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl">timeout <span class="m">0</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.</p>Controlling Zigbee devices with MQTThttps://balkian.com/p/controlling-zigbee-devices-with-mqtt/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/p/controlling-zigbee-devices-with-mqtt/<p>This is a short tutorial on connecting a zigbee device (an Aqara cube) +to an MQTT server, so you can control your zigbee devices from the +network.</p> +<p>If you&rsquo;re anything like me, you&rsquo;re probably a sucker for IoT devices. +For a long time, I&rsquo;ve been using WiFi-enabled lights, and Amazon dash +buttons to control them. To keep these (cheap Chinese) internet enabled +devices away from your network and their respective cloud services, +you&rsquo;ll probably want to set up a dedicated network in your router (more +on this on a future post, maybe). Another disadvantage of WiFi devices +is that they&rsquo;re relatively power hungry.</p> +<p>A popular alternative is using ZigBee for communication. It is a +dedicated protocol similar to bluetooth (BLE), with lower power +requirements and bitrate.</p> +<p>Take the (super cute) aqara cube as an example. It is a small cube that +detects rotation on all of its axes, and tapping events. Here&rsquo;s a +video:</p> +<div class="video-wrapper"> +<iframe loading="lazy" +src="https://www.youtube.com/embed/5YtqG1wEnng" +allowfullscreen +title="YouTube Video" +> +</iframe> +</div> +<p>To connect to zigbee devices you will need a zigbee enabled gateway +(a.k.a. hub), which connects to your WiFi network and your zigbee +devices. Once again, this means adding an internet-enabled device to +your home, and probably a couple of cloud services.</p> +<p>As an alternative, you can set up your own zigbee gateway, and control +it to your home automation platform of choice (e.g. home assistant). We +will cover how to set up a zigbee2mqtt gateway that is also connected to +an MQTT server, so you can use MQTT to control your devices and get +notifications.</p> +<p>What you need:</p> +<ul> +<li><a class="link" href="https://www.aliexpress.com/item/Original-Xiaomi-Mi-Aqara-Cube-Smart-Home-Controller-6-Action-Operation-Fr-Home-Device-Zigbee-Version/32892947622.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>Aqara +cube</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/Wireless-Zigbee-CC2531-CC2540-Zigbee-Sniffer-Bluetooth-BLE-4-0-Dongle-Capture-Module-USB-Programmer-Downloader/32907587711.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC2531 zigbee +sniffer</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/CFSUNBIRD-CC-DEBUGGER-Debugger-and-Programmer-for-RF-System-on-Chips-TI-ORIGINAL-Fast-hipping/32813122315.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC-debugger</a>.</li> +</ul> +<p>You will need to flash your sniffer. For that, you only need to follow +the instructions from the <a class="link" href="https://koenkk.github.io/zigbee2mqtt/" target="_blank" rel="noopener" +>zigbee2mqtt +documentation</a>.</p> +<p>Once you&rsquo;re done flashing, you&rsquo;re ready to set up the zigbee2mqtt +server. For convenience, I wrote a simple docker-compose to deploy a +zigbee2mqtt server and a test mosquitto server:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;2.1&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">zigbee2mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">koenkk/zigbee2mqtt</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">zigbee2mqtt </span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./z2m-data/:/app/data/</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">devices</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="s2">&#34;/dev/ttyACM0&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">eclipse-mosquitto</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">1883</span><span class="p">:</span><span class="m">1883</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">9001</span><span class="p">:</span><span class="m">9001</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./mosquitto.conf:/mosquitto/config/mosquitto.conf</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hass</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">overlay</span><span class="w"> +</span></span></span></code></pre></td></tr></table> +</div> +</div><p>You can test your installation with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">❯ mosquitto_sub -h localhost -p <span class="m">1883</span> -t <span class="s1">&#39;zigbee2mqtt/#&#39;</span> +</span></span><span class="line"><span class="cl">online +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:149,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;rotate_right&#34;</span>,<span class="s2">&#34;angle&#34;</span>:12.8<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;slide&#34;</span>,<span class="s2">&#34;side&#34;</span>:2<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:120<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;wakeup&#34;</span><span class="o">}</span></span></span></code></pre></td></tr></table> +</div> +</div> +<p>zigbee2mqtt supports the following events for the aqara cube: shake, +wakeup, fall, tap, slide, flip180, flip90, rotate_left and +rotate_right. Every event has additional information, such as the sides +involved, or the degrees turned.</p> +<p>Now you are ready to set up home assistant support in zigbee2mqtt +following <a class="link" href="https://koenkk.github.io/zigbee2mqtt/integration/home_assistant.html" target="_blank" rel="noopener" +>this +guide</a>.</p>Progress bars in pythonhttps://balkian.com/p/progress-bars-in-python/Wed, 28 Sep 2016 18:47:00 +0000https://balkian.com/p/progress-bars-in-python/<p><a class="link" href="https://github.com/noamraph/tqdm" target="_blank" rel="noopener" +>tqdm</a> is a nice way to add progress +bars in the command line or in a jupyter notebook.</p> +<p><img src="https://camo.githubusercontent.com/48838faaa8d00ea297f18e5bf55d3c6bb4e0ba6b/68747470733a2f2f692e696d6775722e636f6d2f686539417735432e676966" +loading="lazy" +alt="image" +></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">tqdm</span> <span class="kn">import</span> <span class="n">tqdm</span> +</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)):</span> +</span></span><span class="line"><span class="cl"> <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Sharing dotfileshttps://balkian.com/p/sharing-dotfiles/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/p/sharing-dotfiles/<p>Today&rsquo;s post is half a quick note, half public shaming. In other words, +it is a reminder to be very careful with OAuth tokens and passwords.</p> +<p>As part of moving to emacs, I starting using the incredibly useful +<a class="link" href="https://github.com/defunkt/gist.el" target="_blank" rel="noopener" +>gh.el</a>. When you first use it, the +extension saves either your password or an OAuth token in your +.gitconfig file. This is cool and convenient, unless you <a class="link" href="https://github.com/balkian/dotfiles" target="_blank" rel="noopener" +>happen to be +publishing your .gitconfig file in a public +repo</a>.</p> +<p>So, how can you still share your gitconfig without sharing your +password/token with the rest of the world? Since Git 1.7.0, you can +<a class="link" href="http://stackoverflow.com/questions/1557183/is-it-possible-to-include-a-file-in-your-gitconfig" target="_blank" rel="noopener" +>include other files in your +gitconfig</a>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[include] +</span></span><span class="line"><span class="cl"> path = ~/.gitconfig_secret +</span></span></code></pre></td></tr></table> +</div> +</div><p>And now, in your .gitconfig_secret file, you just have to add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[github] +</span></span><span class="line"><span class="cl"> user = balkian +</span></span><span class="line"><span class="cl"> token = &#34;&lt; Your secret token &gt;&#34; +</span></span></code></pre></td></tr></table> +</div> +</div>Zoterohttps://balkian.com/p/zotero/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/p/zotero/<p><a class="link" href="https://www.zotero.org/" target="_blank" rel="noopener" +>Zotero</a> is an Open Source tool that lets you +organise your bibliography, syncing it with the cloud. Unlike other +alternatives such as <a class="link" href="http://www.mendeley.com" target="_blank" rel="noopener" +>Mendeley</a>, Zotero can +upload the attachments and data to a private cloud via WebDav.</p> +<p>If you use nginx as your web server, know that even though it provides +partial support for webdav, Zotero needs more than that. Hence, you will +need another webdav server, and optionally let nginx proxy to it. This +short post provides the basics to get that set-up working under +Debian/Ubuntu.</p> +<h2 id="setting-up-apache">Setting up Apache +</h2><p>First we need to install Apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install apache2 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Change the head of &ldquo;/etc/apache2/sites-enabled/000-default&rdquo; to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;VirtualHost</span> <span class="s">*:880</span><span class="nt">&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Then, create a file /etc/apache2/sites-available/webdav:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nb">Alias</span> <span class="sx">/dav</span> <span class="sx">/home/webdav/dav</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/dav</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">on</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Order</span> Allow,Deny +</span></span><span class="line"><span class="cl"> <span class="nb">Allow</span> from <span class="k">all</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">On</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Options</span> +Indexes +</span></span><span class="line"><span class="cl"> <span class="nb">AuthType</span> Basic +</span></span><span class="line"><span class="cl"> <span class="nb">AuthName</span> DAV +</span></span><span class="line"><span class="cl"> <span class="nb">AuthBasicProvider</span> file +</span></span><span class="line"><span class="cl"> <span class="nb">AuthUserFile</span> <span class="sx">/home/webdav/.htpasswd</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Require</span> valid-user +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Ideally, you want your webdav folders to be private, adding +authentication to them. So you need to create the webdav and zotero +users and add the passwords to an htpasswd file. Even though you could +use a single user, since you will be configuring several clients with +your credentials I encourage you to create the zotero user as well. This +way you can always change the password for zotero without affecting any +other application using webdav.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo adduser webdav +</span></span><span class="line"><span class="cl">sudo htpasswd -c /home/webdav/.htpasswd webdav +</span></span><span class="line"><span class="cl">sudo htpasswd /home/webdav/.htpasswd zotero +</span></span><span class="line"><span class="cl">sudo mkdir -p /home/webdav/dav/zotero +</span></span></code></pre></td></tr></table> +</div> +</div><p>Enable the site and restart apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo a2enmod webdav +</span></span><span class="line"><span class="cl">sudo a2enmod dav_fs +</span></span><span class="line"><span class="cl">sudo a2ensite webdav +</span></span><span class="line"><span class="cl">sudo service apache2 restart +</span></span></code></pre></td></tr></table> +</div> +</div><p>At this point everything should be working at +<a class="link" href="http://" target="_blank" rel="noopener" +>http://</a>&lt;your_host&gt;:880/dav/zotero</p> +<h2 id="setting-up-nginx">Setting up NGINX +</h2><p>After the Apache side is working, we can use nginx as a proxy to get +cleaner URIs. In your desired site/location, add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/dav</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="kn">client_max_body_size</span> <span class="s">20M</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:880</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now just reload nginx:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo service nginx force-reload +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extras">Extras +</h2><ul> +<li><a class="link" href="http://zoteroreader.com/" target="_blank" rel="noopener" +>Zotero Reader</a> - HTML5 client</li> +<li><a class="link" href="https://github.com/ajlyon/zandy" target="_blank" rel="noopener" +>Zandy</a> - Android Open Source +client</li> +</ul>Proxies with Apache and pythonhttps://balkian.com/p/proxies-with-apache-and-python/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/p/proxies-with-apache-and-python/<p>This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name <em>application</em>. Hence, wsgi:application.</p> +<h2 id="gunicorn">Gunicorn +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPass</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPassReverse</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">RequestHeader</span> set SCRIPT_NAME <span class="s2">&#34;/myapp/&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p><strong>Important</strong>: <em>SCRIPT_NAME</em> and the end of <em>ProxyPass</em> URL <strong>MUST BE +THE SAME</strong>. Otherwise, Gunicorn will fail miserably.</p> +<p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">venv/bin/gunicorn -w <span class="m">4</span> -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="uwsgi">UWSGI +</h2><p>This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">SetHandler</span> uwsgi_handler +</span></span><span class="line"><span class="cl"> <span class="nb">uWSGISocker</span> <span class="m">127.0.0.1</span>:8888 +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uwsgi --socket 127.0.0.1:8888 -w wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extra-supervisor">Extra: Supervisor +</h2><p>If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[unix_http_server]</span> +</span></span><span class="line"><span class="cl"><span class="na">file</span><span class="o">=</span><span class="s">/tmp/myapp.sock ; path to your socket file</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisord]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisor.log</span> +</span></span><span class="line"><span class="cl"><span class="na">childlogdir</span> <span class="o">=</span> <span class="s">%(here)s/logs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[rpcinterface:supervisor]</span> +</span></span><span class="line"><span class="cl"><span class="na">supervisor.rpcinterface_factory</span> <span class="o">=</span> <span class="s">supervisor.rpcinterface:make_main_rpcinterface</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisorctl]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisorctl.log</span> +</span></span><span class="line"><span class="cl"><span class="na">serverurl</span><span class="o">=</span><span class="s">unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[program:myapp]</span> +</span></span><span class="line"><span class="cl"><span class="na">command</span> <span class="o">=</span> <span class="s">venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application</span> +</span></span><span class="line"><span class="cl"><span class="na">directory</span> <span class="o">=</span> <span class="s">%(here)s</span> +</span></span><span class="line"><span class="cl"><span class="na">environment</span> <span class="o">=</span> <span class="s">PATH=%(here)s/venv/bin/</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/myapp.log</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Publishing on PyPihttps://balkian.com/p/publishing-on-pypi/Sat, 27 Sep 2014 10:00:00 +0000https://balkian.com/p/publishing-on-pypi/<p>Developing a python module and publishing it on Github is cool, but most +of the times you want others to download and use it easily. That is the +role of PyPi, the python package repository. In this post I show you how +to publish your package in less than 10 minutes.</p> +<h2 id="choose-a-fancy-name">Choose a fancy name +</h2><p>If you haven&rsquo;t done so yet, take a minute or two to think about this. +To publish on PyPi you need a name for your package that isn&rsquo;t taken. +What&rsquo;s more, a catchy and unique name will help people remember your +module and feel more inclined to at least try it.</p> +<p>The package name should hint what your module does, but that&rsquo;s not +always the case. That&rsquo;s your call. I personally put uniqueness and +memorability over describing the functionality.</p> +<h2 id="create-a-pypirc-configuration-file">Create a .pypirc configuration file +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">[distutils] # this tells distutils what package indexes you can push to</span> +</span></span><span class="line"><span class="cl"><span class="na">index-servers</span> <span class="o">=</span><span class="s"> +</span></span></span><span class="line"><span class="cl"><span class="s"> pypi # the live PyPI +</span></span></span><span class="line"><span class="cl"><span class="s"> pypitest # test PyPI</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypi] # authentication details for live PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://pypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span><span class="line"><span class="cl"><span class="na">password</span> <span class="o">=</span> <span class="s">{ your_password } # not necessary</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypitest] # authentication details for test PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://testpypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>As you can see, you need to register both in the <a class="link" href="https://pypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>main pypi +repository</a> and +the <a class="link" href="https://testpypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>testing +server</a>. The +usernames and passwords might be different, that is up to you!</p> +<h2 id="prepare-your-package">Prepare your package +</h2><p>This should be the structure:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">root-dir/ # Any name you want +</span></span><span class="line"><span class="cl"> setup.py +</span></span><span class="line"><span class="cl"> setup.cfg +</span></span><span class="line"><span class="cl"> LICENSE.txt +</span></span><span class="line"><span class="cl"> README.md +</span></span><span class="line"><span class="cl"> mypackage/ +</span></span><span class="line"><span class="cl"> __init__.py +</span></span><span class="line"><span class="cl"> foo.py +</span></span><span class="line"><span class="cl"> bar.py +</span></span><span class="line"><span class="cl"> baz.py +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="setupcfg">setup.cfg +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[metadata]</span> +</span></span><span class="line"><span class="cl"><span class="na">description-file</span> <span class="o">=</span> <span class="s">README.md</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The markdown README is the <em>de facto</em> standard in Github, but you can +also use rST (reStructuredText), the standard in the python community.</p> +<h3 id="setuppy">setup.py +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">distutils.core</span> <span class="kn">import</span> <span class="n">setup</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">setup</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;mypackage&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;mypackage&#39;</span><span class="p">],</span> <span class="c1"># this must be the same as the name above</span> +</span></span><span class="line"><span class="cl"> <span class="n">version</span> <span class="o">=</span> <span class="s1">&#39;{ version }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">description</span> <span class="o">=</span> <span class="s1">&#39;{ description }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">author</span> <span class="o">=</span> <span class="s1">&#39;{ name }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">email</span> <span class="o">=</span> <span class="s1">&#39;{ email }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{package}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="c1"># URL to the github repo</span> +</span></span><span class="line"><span class="cl"> <span class="n">download_url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{repo}</span><span class="s1">/tarball/</span><span class="si">{version}</span><span class="s1">&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">keywords</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;websockets&#39;</span><span class="p">,</span> <span class="s1">&#39;display&#39;</span><span class="p">,</span> <span class="s1">&#39;d3&#39;</span><span class="p">],</span> <span class="c1"># list of keywords that represent your package</span> +</span></span><span class="line"><span class="cl"> <span class="n">classifiers</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>You might notice that the download_url points to a Github URL. We could +host our package anywhere, but Github is a convenient option. To create +the tarball and the zip packages, you only need to tag a tag in your +repository and push it to github:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git tag <span class="o">{</span>version<span class="o">}</span> -m <span class="s2">&#34;{ Description of this tag/version}&#34;</span> +</span></span><span class="line"><span class="cl">git push --tags origin master +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="push-to-the-testingmain-pypi-server">Push to the testing/main pypi server +</h2><p>It is advisable that you try your package on the test repository and fix +any problems first. The process is simple:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">python setup.py register -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> python setup.py sdist upload -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>If everything went as expected, you can now install your package through +pip and browse your package&rsquo;s page. For instance, check my senpy +package: <a class="link" href="https://pypi.python.org/pypi/senpy" target="_blank" rel="noopener" +>https://pypi.python.org/pypi/senpy</a></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install senpy +</span></span></code></pre></td></tr></table> +</div> +</div>Updating EuroLoveMaphttps://balkian.com/p/updating-eurolovemap/Thu, 27 Mar 2014 14:00:00 +0000https://balkian.com/p/updating-eurolovemap/<p>As part of the <a class="link" href="http://www.opener-project.org/2013/07/18/opener-hackathon-in-amsterdam/" target="_blank" rel="noopener" +>OpeNER +hackathon</a> +we decided to build a prototype that would allow us to compare how +different countries feel about several topics. We used the OpeNER +pipeline to get the sentiment from a set of newspaper articles we +gathered from media in several languages. Then we aggregated those +articles by category and country (using the source of the article or the +language it was written in), obtaining the &ldquo;overall feeling&rdquo; of each +country about each topic. Then, we used some fancy JavaScript to make +sense out of the raw information.</p> +<p>It didn&rsquo;t go too bad, it turns out <a class="link" href="http://eurosentiment.eu/wp-content/uploads/2013/07/BOLv9qnCIAAJEek.jpg" target="_blank" rel="noopener" +>we +won</a>.</p> +<p>Now, it was time for a face-lift. I used this opportunity to play with +new technologies and improve it:</p> +<ul> +<li>Using Flask, this time using python 3.3 and Bootstrap 3.0</li> +<li>Cool HTML5+JS cards (thanks to +<a class="link" href="http://pastetophone.com" target="_blank" rel="noopener" +>pastetophone</a>)</li> +<li>Automatic generation of fake personal data to test the interface</li> +<li>Obfuscation of personal emails</li> +</ul> +<p>The result can be <a class="link" href="http://eurolovemap.herokuapp.com/" target="_blank" rel="noopener" +>seen here</a>.</p> +<h2 id="publishing-a-python-3-app-on-heroku">Publishing a Python 3 app on Heroku +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkvirtualenv -p /usr/bin/python3.3 eurolovemap +</span></span></code></pre></td></tr></table> +</div> +</div><p>Since Heroku uses python 2.7 by default, we have to tell it which +version we want, although it supports python 3.4 as well. I couldn&rsquo;t +get python 3.4 working using the +<a class="link" href="https://launchpad.net/~fkrull/&#43;archive/deadsnakes" target="_blank" rel="noopener" +>deadsnakes</a> ppa, so +I used python 3.3 instead, which works fine but is not officially +supported. Just create a file named <em>runtime.txt</em> in your project root, +with the python version you want to use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python-3.3.1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Don&rsquo;t forget to freeze your dependencies so Heroku can install them: +<code>bash pip freze &gt; requirements.txt</code></p> +<h2 id="publishing-personal-emails">Publishing personal emails +</h2><p>There are really sophisticated and effective ways to obfuscate personal +emails so that spammers cannot easily grab yours. However, this time I +needed something really simple to hide our emails from the simplest form +of crawlers. Most of the team are in academia somehow, so in the end all +our emails are available in sites like Google Scholar. Anyway, nobody +likes getting spammed so I settled for a custom <a class="link" href="http://en.wikipedia.org/wiki/Caesar_cipher" target="_blank" rel="noopener" +>Caesar +cipher</a>. Please, don&rsquo;t use +it for any serious application if you are concerned about being spammed.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">blur_email</span><span class="p">(</span><span class="n">email</span><span class="p">):</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">email</span><span class="p">])</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>And this is the client side:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">&#39;profile-email&#39;</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">e</span> <span class="k">in</span> <span class="nx">elems</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">blur</span> <span class="o">=</span> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">email</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">s</span> <span class="k">in</span> <span class="nx">blur</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">blur</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="o">+</span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="nx">a</span><span class="o">-</span><span class="mi">5</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Unfortunately, this approach does not hide your email from anyone using +<a class="link" href="http://phantomjs.org/" target="_blank" rel="noopener" +>PhantomJS</a>, +<a class="link" href="http://zombie.labnotes.org/" target="_blank" rel="noopener" +>ZombieJS</a> or similar. For that, other +approaches like generating a picture with the address would be +necessary. Nevertheless, it is overkill for a really simple ad-hoc +application with custom formatting and just a bunch of emails that would +easily be grabbed manually.</p> +<h2 id="generation-of-fake-data">Generation of fake data +</h2><p>To test the contact section of the site, I wanted to populate it with +fake data. <a class="link" href="https://github.com/joke2k/faker" target="_blank" rel="noopener" +>Fake-Factory</a> is an amazing +library that can generate fake data of almost any kind: emails, +association names, acronyms&hellip; It even lets you localise the results +(get Spanish names, for instance) and generate factories for certain +classes (à la Django).</p> +<p>But I also wanted pictures, enter <a class="link" href="http://lorempixel.com/" target="_blank" rel="noopener" +>Lorem Pixel</a>. +With its API you can generate pictures of almost any size, for different +topics (e.g. nightlife, people) and with a custom text. You can even use +an index, so it will always show the same picture.</p> +<p>For instance, the picture below is served through Lorem Pixel.</p> +<p><img src="http://lorempixel.com/400/200/nightlife/" +loading="lazy" +></p> +<p>By the way, if you only want cat pictures, take a look at +<a class="link" href="http://placekitten.com/" target="_blank" rel="noopener" +>Placekitten</a>. And for NSFW text, there&rsquo;s the +<a class="link" href="http://slipsum.com/" target="_blank" rel="noopener" +>Samuel L. Jackson Ipsum</a></p>Remove git files with globbinghttps://balkian.com/p/remove-git-files-with-globbing/Thu, 22 Aug 2013 23:14:00 +0000https://balkian.com/p/remove-git-files-with-globbing/<p>A simple trick. If you want to remove all the &lsquo;.swp&rsquo; files from a git +repository, just use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git rm --cached <span class="s1">&#39;**.swp&#39;</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Creating my webhttps://balkian.com/p/creating-my-web/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/p/creating-my-web/<p>I&rsquo;ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +<a class="link" href="http://jekyllrb.com" target="_blank" rel="noopener" +>Jekyll</a> will automatically generate static pages +for my posts every time I commit anything new to this repository.</p> +<p>But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.</p> +<p>I hadn&rsquo;t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven&rsquo;t included +any Javascript code in the page. Probably I will use some to add my +<a class="link" href="http://gist.github.com/balkian" target="_blank" rel="noopener" +>gists</a> and +<a class="link" href="http://github.com/balkian" target="_blank" rel="noopener" +>repositories</a>, but we will see about that.</p> +<p>I think the code speaks for itself, so you can check out <a class="link" href="http://github.com/balkian/balkian.github.com" target="_blank" rel="noopener" +>my repository +on Github</a>. You can clone +and deploy it easily like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone +</span></span><span class="line"><span class="cl">https://github.com/balkian/balkian.github.com <span class="nb">cd</span> balkian.github.com +</span></span><span class="line"><span class="cl">jekyll serve -w +</span></span></code></pre></td></tr></table> +</div> +</div><p>I will keep updating this post with information about:</p> +<ul> +<li>Some Jekyll plugins that might be useful</li> +<li>What CSS tricks I learnt</li> +<li>The webfonts I used</li> +<li>The badge on the left side of the page</li> +</ul>Abouthttps://balkian.com/about/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/about/<p>Hello there, stranger! &#x1f44b;</p> +<h2 id="about-me">About me +</h2><p>My name is Fernando, and I like learning and solving hard problems. +Especially when it comes to computers, engineering and languages.</p> +<p>I currently work at the Technical University of Madrid (UPM) as an assistant professor in the school of Telecommunications Engineering. +You can check out my <a class="link" href="https://balkian.com/page/projects" >previous projects</a>, and <a class="link" href="https://scholar.google.com/citations?user=JLNusZ8AAAAJ&amp;hl=en" target="_blank" rel="noopener" +>my publications</a>. +Feel free to get in touch through the comment section, an e-mail (<code>my first initial</code> <code>@sanchezrada.es</code>) or any other platform. +I am always happy to help and collaborate.</p> +<h2 id="about-this-blog">About this blog +</h2><p>I use this blog for future reference, to write down some of the lessons I learn so. +I also see it as an exercise in reflection and sorting out my ideas. +Although I mostly do this for myself, to keep some lasting notes for the future, I also do it in hopes it might help someone like me in the future.</p> +<p>Each post is an independent note. +To keep some structure I will try to stick to general categories (e.g., programming, project management, linux), and add meaningful tags to help you and me find this information in the future. +You may also use the <a class="link" href="https://balkian.com/search" >search bar</a> if you are looking for something specific and wondering if I&rsquo;ve covered it. +For short thematically connected snippets and tips, I keep a dedicated section with <a class="link" href="https://balkian.com/cheatsheet" >Cheatsheets</a>.</p>Emacs: show plain text versionhttps://balkian.com/p/emacs-show-plain-text-version/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/emacs-show-plain-text-version/<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">font-lock-mode</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Fixing HDMI flickeringhttps://balkian.com/p/fixing-hdmi-flickering/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/fixing-hdmi-flickering/<img src="https://balkian.com/img/rpi.png" alt="Featured image of post Fixing HDMI flickering" /><p>Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_drive</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_group</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_mode</span><span class="o">=</span><span class="mi">42</span> +</span></span><span class="line"><span class="cl"> <span class="n">disable_overscan</span><span class="o">=</span><span class="mi">1</span> +</span></span><span class="line"><span class="cl"> <span class="n">config_hdmi_boost</span><span class="o">=</span><span class="mi">7</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Linkshttps://balkian.com/links/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/links/Linux Cheatsheethttps://balkian.com/cheatsheet/linux/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/linux/<img src="https://balkian.com/img/linux.png" alt="Featured image of post Linux Cheatsheet" /><h2 id="black-screen-and-lightdm-doesnt-unlock">Black screen and LightDM doesn&rsquo;t unlock +</h2><p>Add this to your /etc/lightdm/lightdm.conf file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[LightDM]</span> +</span></span><span class="line"><span class="cl"><span class="na">logind-check-graphical</span><span class="o">=</span><span class="s">true</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>##Edit previous commands</p> +<p><code>fc</code> is a shell builtin to list and edit previous commands in an editor. +In addition to editing a single line (which you can also do with <code>C-x C-e</code>), it also allows you to edit and run several lines at the same time. +You use it like this:</p> +<p>List previous commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10259</span> nvim deploy.sh +</span></span><span class="line"><span class="cl">10260* <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> <span class="nb">cd</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>List commands with date (in zsh)</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -ld +</span></span><span class="line"><span class="cl">10260* 19:38 <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* 19:38 nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 19:40 <span class="nb">fc</span> -l +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can add the date too:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -fld +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 1/10/2019 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 1/10/2019 19:40 <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10264</span> 1/10/2019 19:40 <span class="nb">fc</span> -ld +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can edit a range of commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> <span class="m">10262</span> <span class="m">10264</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The range can be relative to the current position, so the previous command is equivalent to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -3 -1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>If you save and exit, all commands are executed as a script, and it will be added to your history.</p> +<p>Source: <a class="link" href="https://shapeshed.com/unix-fc/" target="_blank" rel="noopener" +>https://shapeshed.com/unix-fc/</a></p> +<h2 id="prevent-logoff-from-killing-tmux-sessions">Prevent logoff from killing tmux sessions +</h2><p>Lately I&rsquo;ve noticed that logging out of i3, intentionally or when i3 fails, would also kill any tmux or emacs sessions. +This is extremely annoying.</p> +<p>This is caused by a new default in logind (systemd&rsquo;s login) to kill user process on logoff. +You can revert this setting in your logind.conf (<code>/etc/systemd/logind.conf</code>):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">KillUserProcesses</span><span class="o">=</span><span class="s">no</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Or only for a specific process (e.g., tmux):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemd-run --scope --user tmux +</span></span></code></pre></td></tr></table> +</div> +</div><p>Source: <a class="link" href="https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session" target="_blank" rel="noopener" +>https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session</a></p> +<h2 id="upload-a-temporary-file">Upload a temporary file +</h2><p>Sometimes you just need to copy/paste a file from a server, and copying from the terminal can be a hassle. +These two services are command-line &ldquo;pastebins&rdquo; just one curl away:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span><span class="lnt">8 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F <span class="s1">&#39;sprunge=&lt;-&#39;</span> http://sprunge.us +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> curl -F <span class="s1">&#39;f:1=&lt;-&#39;</span> ix.io +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F<span class="s2">&#34;file=@-&#34;</span> https://ttm.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h1 id="install-fortinet-sslvpn-support-for-networkmanager">Install Fortinet SSLVPN support for NetworkManager +</h1><p>UPM (Universidad Politécnica de Madrid) uses a propriatary VPN solution. +The instructions for GNU/Linux on their website involve downloading a specific client (<code>.tar.gz</code>) and manually running it. +That works, but it is kind of a hassle. +A much more convenient alternative is installing this NetworkManager plugin:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pacman -Sy networkmanager-fortisslvpn +</span></span><span class="line"><span class="cl"><span class="c1"># Or apt get install networkmanager-fortisslvpn </span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now you can simply add a new VPN connection in NetworkManager and manage it as you would any other connection.</p>Projectshttps://balkian.com/projects/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/projects/<h1 id="ongoing-projects">Ongoing Projects +</h1><ul> +<li><a class="link" href="https://soilsim.readthedocs.io" target="_blank" rel="noopener" +>Soil</a>: an agent-based simulator for social networks based on nx-sim and networkx.</li> +<li><a class="link" href="https://github.com/balkian/soilent" target="_blank" rel="noopener" +>Soilent</a>: an efficient scheduler for soil using rust and pyo3.</li> +<li><a class="link" href="https://senpy.readthedocs.io" target="_blank" rel="noopener" +>Senpy</a>: a framework for semantic sentiment and emotion analysis services.</li> +</ul> +<h1 id="past-projects">Past Projects +</h1><ul> +<li><a class="link" href="http://gsi.dit.upm.es/ontologies/onyx" target="_blank" rel="noopener" +>Onyx</a>: an ontology for emotion analysis that includes concepts from W3C&rsquo;s provenance.</li> +<li><a class="link" href="https://github.com/balkian/ESP8266_Clock_NTP" target="_blank" rel="noopener" +>ESP8266 Clock NTP</a>: a simple clock display using arduino, the ESP8266 and NTP (network time protocol).</li> +<li><a class="link" href="https://github.com/balkian/shinesp" target="_blank" rel="noopener" +>Shine ESP</a>: control an ws2812b LED strip over the network with an ESP8266.</li> +<li><a class="link" href="https://github.com/balkian/bitter" target="_blank" rel="noopener" +>Bitter</a>: a wrapper and CLI over the (now defunct) Twitter API to researchers to download Twitter data much faster using multiple accounts.</li> +<li><a class="link" href="http://gsi.dit.upm.es/ontologies/marl" target="_blank" rel="noopener" +>Marl</a>: I updated this ontology, originally created by Adam Westerski, to make it compatible with the W3C&rsquo;s provenance ontology.</li> +<li><a class="link" href="http://github.com/balkian/hermes" target="_blank" rel="noopener" +>Hermes</a>: one of my first projects, developed together with David Pérez as the special custom assignment in one of our courses. Hermes is an affective bot designed to mimic the behavour of humans. It included a plug-in system for its sensors and actuators. The information from its sensors changed its emotional state, which was shown via its actuators. Among others, it could fetch inforation from Twitter or its host system and change the expressions of an external Face made with servo motors or speak via its Text-To-Speech software. For instance, it could detect it was running out of battery, showing a sad face and sending an alerting tweet. You can see it in action in these two youtube videos: <a class="link" href="http://www.youtube.com/watch?v=KnEYahPD9z4" target="_blank" rel="noopener" +>Part 1</a> and <a class="link" href="http://www.youtube.com/watch?v=lQZldCTPEJc" target="_blank" rel="noopener" +>Part 2</a>.</li> +<li><a class="link" href="http://github.com/gsi-upm/maia" target="_blank" rel="noopener" +>Maia</a>: the Modular Architecture for Intelligent Agents is an evented agent architecture that aims to update the classical frameworks for intelligent agents with the concepts emerged from the Live Web.</li> +<li><a class="link" href="http://github.com/eestec/eestec.portal" target="_blank" rel="noopener" +>EESTEC.net</a>: the Plone based official portal of EESTEC. I fixed some bugs and implemented basic features.</li> +</ul> +<p>For more information, check my list of public repositories in <a href="http://github.com/balkian"><i class="fab fa-github"> Github</i></a>.</p>Pythonhttps://balkian.com/cheatsheet/python/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/python/<img src="https://balkian.com/img/python.png" alt="Featured image of post Python" /><h2 id="interesting-libraries">Interesting libraries +</h2><h3 id="tqdm"><a class="link" href="https://github.com/tqdm/tqdm" target="_blank" rel="noopener" +>TQDM</a> +</h3><p>From tqdm&rsquo;s github repository:</p> +<blockquote> +<p>tqdm means &ldquo;progress&rdquo; in Arabic (taqadum, تقدّم) and an abbreviation for &ldquo;I love you so much&rdquo; in Spanish (te quiero demasiado).</p></blockquote> +<p><img src="https://raw.githubusercontent.com/tqdm/tqdm/master/images/tqdm.gif" +loading="lazy" +alt="TQDM in action" +></p> +<h2 id="tools">Tools +</h2><h3 id="uv"><a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a> +</h3><p>🚀 A single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +⚡️ 10-100x faster than pip.</p> +<ul> +<li>Provides comprehensive project management, with a universal lockfile.</li> +<li>Runs scripts, with support for inline dependency metadata.</li> +<li>Installs and manages Python versions.</li> +<li>Runs and installs tools published as Python packages.</li> +<li>Includes a pip-compatible interface for a performance boost with a familiar CLI.</li> +<li>Supports Cargo-style workspaces for scalable projects.</li> +<li>Disk-space efficient, with a global cache for dependency deduplication.</li> +<li>Installable without Rust or Python via curl or pip.</li> +<li>Supports macOS, Linux, and Windows.</li> +</ul>Searchhttps://balkian.com/search/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/search/ \ No newline at end of file diff --git a/links/index.html b/links/index.html new file mode 100644 index 0000000..348a422 --- /dev/null +++ b/links/index.html @@ -0,0 +1,21 @@ +Links +

Links

Some pointers to useful resources.

+Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/bridging-rdf-json-ld-and-dataclasses/index.html b/p/bridging-rdf-json-ld-and-dataclasses/index.html new file mode 100644 index 0000000..b6519eb --- /dev/null +++ b/p/bridging-rdf-json-ld-and-dataclasses/index.html @@ -0,0 +1,70 @@ +Bridging RDF, JSON-LD and Dataclasses +

Bridging RDF, JSON-LD and Dataclasses

+
+

In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.

The rdflib way

rdflib only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, Entity.

1
+2
+3
+4
+5
+
entry = Entry()
+
+entry['vocab:property'] = 25
+
+print(entry.jsonld())
+

Would print something like this:

1
+2
+3
+4
+5
+
{
+ "@id": ":Entry_202505....",
+ "@type": "prefix:Entity",
+ "vocab:property": 25
+}
+

Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the

Pros:

  • Flexible/extensible
  • Lightweight. This is mostly JSON-LD in Python’s clothing.
  • Naturally maps to both rdflib and writing json-ld

Cons:

  • Discoverability. Documentation and examples are needed to know which attributes to use
  • Error-prone. It is easy to misuse a property, or introduce typos
  • Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.

The object-oriented way

An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like pydantic that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.

This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of senpy, where entities are annotated by different plugins, each providing a different set of annotations.

Pros:

  • Discoverability. All possible attributes are known ahead of time, including their possible types.
  • Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.

Cons:

  • Rigidity. Adding new types of annotations requires modifying the models, in the main module.
  • Polymorphism.

A hybrid approach

Whichever solution is chosen in the end, it needs to:

  • Make it easy and error-proof to add the most common types of annotations
  • Allow for additional annotations/attributes to be added
  • Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones
  • Allow for deserialization of custom types
  • Allow multiple consumers to add their own annotations
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/controlling-zigbee-devices-with-mqtt/index.html b/p/controlling-zigbee-devices-with-mqtt/index.html new file mode 100644 index 0000000..7680824 --- /dev/null +++ b/p/controlling-zigbee-devices-with-mqtt/index.html @@ -0,0 +1,114 @@ +Controlling Zigbee devices with MQTT +

Controlling Zigbee devices with MQTT

+
+

This is a short tutorial on connecting a zigbee device (an Aqara cube) +to an MQTT server, so you can control your zigbee devices from the +network.

If you’re anything like me, you’re probably a sucker for IoT devices. +For a long time, I’ve been using WiFi-enabled lights, and Amazon dash +buttons to control them. To keep these (cheap Chinese) internet enabled +devices away from your network and their respective cloud services, +you’ll probably want to set up a dedicated network in your router (more +on this on a future post, maybe). Another disadvantage of WiFi devices +is that they’re relatively power hungry.

A popular alternative is using ZigBee for communication. It is a +dedicated protocol similar to bluetooth (BLE), with lower power +requirements and bitrate.

Take the (super cute) aqara cube as an example. It is a small cube that +detects rotation on all of its axes, and tapping events. Here’s a +video:

To connect to zigbee devices you will need a zigbee enabled gateway +(a.k.a. hub), which connects to your WiFi network and your zigbee +devices. Once again, this means adding an internet-enabled device to +your home, and probably a couple of cloud services.

As an alternative, you can set up your own zigbee gateway, and control +it to your home automation platform of choice (e.g. home assistant). We +will cover how to set up a zigbee2mqtt gateway that is also connected to +an MQTT server, so you can use MQTT to control your devices and get +notifications.

What you need:

You will need to flash your sniffer. For that, you only need to follow +the instructions from the zigbee2mqtt +documentation.

Once you’re done flashing, you’re ready to set up the zigbee2mqtt +server. For convenience, I wrote a simple docker-compose to deploy a +zigbee2mqtt server and a test mosquitto server:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
version: '2.1'
+services:
+  zigbee2mqtt:
+    image: koenkk/zigbee2mqtt
+    container_name: zigbee2mqtt 
+    restart: always
+    volumes:
+      - ./z2m-data/:/app/data/
+    devices:
+      - "/dev/ttyACM0"
+    networks:
+        - hass
+  mqtt:
+    image: eclipse-mosquitto
+    ports:
+       - 1883:1883
+       - 9001:9001 
+    networks:
+        - hass
+    volumes:
+      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf
+networks:
+  hass:
+    driver: overlay
+

You can test your installation with:

1
+2
+3
+4
+5
+6
+
❯ mosquitto_sub -h localhost -p 1883 -t 'zigbee2mqtt/#'
+online
+{"battery":17,"voltage":2925,"linkquality":149,"action":"rotate_right","angle":12.8}
+{"battery":17,"voltage":2925,"linkquality":141,"action":"slide","side":2}
+{"battery":17,"voltage":2925,"linkquality":120}
+{"battery":17,"voltage":2925,"linkquality":141,"action":"wakeup"}

zigbee2mqtt supports the following events for the aqara cube: shake, +wakeup, fall, tap, slide, flip180, flip90, rotate_left and +rotate_right. Every event has additional information, such as the sides +involved, or the degrees turned.

Now you are ready to set up home assistant support in zigbee2mqtt +following this +guide.

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/creating-my-web/index.html b/p/creating-my-web/index.html new file mode 100644 index 0000000..dd25839 --- /dev/null +++ b/p/creating-my-web/index.html @@ -0,0 +1,54 @@ +Creating my web +

Creating my web

+
+

I’ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +Jekyll will automatically generate static pages +for my posts every time I commit anything new to this repository.

But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.

I hadn’t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven’t included +any Javascript code in the page. Probably I will use some to add my +gists and +repositories, but we will see about that.

I think the code speaks for itself, so you can check out my repository +on Github. You can clone +and deploy it easily like this:

1
+2
+3
+
git clone
+https://github.com/balkian/balkian.github.com cd balkian.github.com
+jekyll serve -w
+

I will keep updating this post with information about:

  • Some Jekyll plugins that might be useful
  • What CSS tricks I learnt
  • The webfonts I used
  • The badge on the left side of the page
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/efficient-collaboration/index.html b/p/efficient-collaboration/index.html new file mode 100644 index 0000000..a9ffeba --- /dev/null +++ b/p/efficient-collaboration/index.html @@ -0,0 +1,195 @@ +Tips for efficient collaboration +

Tips for efficient collaboration

+
+

Background

TL;DR I work in academia. This post focuses on advice I’d give a younger me to be a more effective supervisor and project lead.

My role in my research group has evolved from individual contributor to project lead that manages a team of multiple students. +This often involves coordinatating with other senior researchers and their teams.

This post is a collection of advice I would have given myself back when I started this journey. +It is also an excuse to reflect on these ideas I’ve been implicitly applying everyday, and maybe learn a few things more in the process.

In my field, projects are often tied to a specific grant or some sort of public funding. +This means that the main concern of the lead is to ensure that the results at the end of the project match the description in the project proposal. +It also typically means maximizing the number of publications related to the project and their overall impact.

To do so, most projects rely on three types of staff: a) senior researchers (post-doc); b) junior researchers (PhD students); and c) interns doing their bachelor’s or master’s thesis. +The level of contribution is generally inversely propotional to the level of mastery of the contributor: PhD students design and develop the main contributions (both software and experimental) under the supervision of senior researchers (advisor/supervisor), and interns take care of tasks that are narrow in scope and not crucial to any academic contribution. +For instance, a PhD student may develop a new model for text classification, and an intern will wrap that model in an HTTP service with a nice UI. +When the service and UI part is intricate and has some potential academic merit, that task may be conducted by a PhD student as part of their thesis. +That was precisely the case with Senpy, which was part of my PhD thesis, and it has since been used by dozens of students to develop services in the context of other research projects.

Reasons to form a team

In my opinion, a team has two advantages over a single contributor. +The first one is that collaboration often generates synergies, leading to surprising and enriching results (a team is greater than the sum of its parts) +Carefully selecting your team members and creating an environment that is conducing to these synergies is a topic on its own, and I will not get too deep into it here.

The second advantage can be summarized as concurrency: tasks can be tackled by more than one member. +This often implies some sort of parallelism. +Tasks tend to be split between different members, in hopes of speeding up the process. +But it can also be beneficial to have concurrency even in the absence of parallelism: different members can take turns solving the same problem. +This is common when the task requires exclusive access to a resource (e.g., performing an experiment on an expensive machine).

Challenges of teams (of students)

Just like in computer science, coordination in a concurrent project involves a non-negligible overhead. +The objective is to minimize this overhead.

I’ve really struggled with managing teams in the past. +Given my context, I often attributed the failures to lack of time (having to juggle teaching and research), lack of training and preparation on the intern’s side (they’re often undergrads), lack of appeal or definition of the task (too academic-y), or any other external factors.

While all those aspects play an important role, some of them are out of our control as a project lead in academia: grants require a certain type of project and workflow, and the quality of our interns is bounded by the quality of our degrees. +So I think it is more constructive to focus on things that we can control. +In other words: we have to play the hand we’re given.

Besides, there is no merit in achieving good results with excellent students/engineers. +They would succeed on their own even if you weren’t there. +The real test for a good leader is succeeding with a subpar team.

In that vein, I’ve reflected on my mistakes as a leader, and the inefficiencies of the teams around me. +I’d classify my failures in the following areas:

  • Delegation (and lack thereof). Piling up too many tasks and blocking progress.
  • Communication. Not having a coherent view of the state of affairs, the details of specific tasks, the general processes to follow, or the priorities of different tasks within a project.
  • Direction (or purpose). Not having a common direction

Delegation

I have a tendency to become a bottleneck in any project I am involved in: many tasks end up depending on me, either directly or indirectly. In the concurrency metaphor, I become a lock for many tasks, and a single executor for the rest. +This issue was not that apparent early on, when most of my work was as an individual contributor or I had the bandwidth to supervise and complete my tasks.

I think it is quite common to feel like delegating a task in these scenarios means:

  1. Defining the task in advance
  2. Choosing an assignee for the task
  3. Setting a deadline for the task
  4. Explaining the task and the relevant context
  5. Replying to multiple questions by e-mail or in person. Extra points when the questions make you wonder if any part of the explanation was ever clear.
  6. Reviewing the results after the deadline
  7. Realizing the assignee misunderstood the task or delivered something not even close to what you agreed upon
  8. Going back to point 3.
  9. When you’re unlucky or short on time: giving up and doing the task yourself

Many times, if felt like delegating tasks only lead to frustration and wasted time. +Especially when compared to the alternative:

  • Defining the task
  • Setting a deadline
  • Finishing the task
  • Profit

Luckily, some students and projects were an exception to this. +They worked autonomously and delivered something beyond the minimum requirements. +This reinforced my helplessness and the feeling that the problem was not being able to work with experienced engineers.

However, I now believe that the truth lies somewhere in between. +Sometimes your circumstances make it quite hard or inefficient to delegate tasks. +And some times may not be good candidates for delegation. +But most of the times you can take advantage of having an extra pair of hands, you just have to do that effectively.

Communication

Small teams rely on implicit knowledge more than they realize. +Even more so if the team is made up of highly specialized people that have worked in the same environment with mostly the same people for years.

Communication is a broad term. +It includes technical and concrete things such as how a certain task should be done. +But it also includes broader things like etiquette, organizational values, and who is more willing to help you out on certain topics on a Friday afternoon.

Here, I would take a page out of Python’s zen and recommend that “explicit is better than implicit”. +Implicit (or tacit) knowledge comes with a whole set of drawbacks:

  • It makes onboarding new users harder. Without a common knowledge base, all knowledge transference has to rely on personal interactions. Even worse, those interactions are probably organized on the spot, and are likely to miss important points.
  • It makes you heavily reliant on your current members (and their memory).
  • It impedes proper evaluationn and progress, since they are not written anywhere.
  • It increases the likelihood of misunderstandings when two members have conflicting beliefs, and makes it harder to detect them until it is too late.
  • It makes contradictions and (unknowingly) changing your mind much more likely. It can happen to the best of us, especially if you are involved in too many projects. When contraditions happen often, your colleagues will learn not to rely on your opinion.

On the other hand, communication has to go both ways. +This means that your newer members need to be able to communicate when something is going wrong or can be improved (backpressure). +They should also feel free to talk about their motivation, state of mind, and feelings, when appropriate. +That last part is quite subjective, of course. +Try to find your - and your organization’s - middle ground between “I don’t care how you feel, just do your job” and “sure, you can go to the Maldives on short notice. Oh, and don’t worry about not having met a deadline in months, I’m sure you’re stressed and can use some vacation but will work remotely if we need you”.

I personally feel a sweet spot is treating your coworkers like people, being empathetic and compassionate. +Part of being a good coworker is fulfilling the duties and obligations you accepted when signing your contract. +First and foremost, because not fulfilling them means someone else will have to work harder to make up for it. +And, secondly, because doing our part is the only way to move the organization (and research) forward.

Direction

By failure in direction I mean not keeping a consistent and shared set of general goals, principles and values in your organization. +In order to really take part in any enterprise, you need to have a clear understanding of the objectives and motivation. +When it comes to specific tasks, what you’re doing is often not as important as the why you’re doing it. +In fact, there may be times where you aren’t truly sure of what exactly it is that you are doing, but you trust the process and the motivation behind the task.

I’ve seen two failure modes in this regard. +The first one is to not have a clear direction. +The end result is that members of the team are not really that committed. +If no other why is provided, we are only left with because they pay me to do it. +And academia is not known to pay particularly well, to be honest.

The other mode is to provide contradicting or incompatible directions. +This can be in a short period of time, leading to the impression that there isn’t really any conviction in the message. +But it can also be done over a longer period of time. +That can be perfectly acceptable, provided that the change in direction is justified and compatible with the principles of the organization.

Failure in direction is somewhat related to communication, but it is subtly different. +An organization can excel at communication, but change their direction constantly. +Arguably, a thorough communication strategy makes radical changes in direction less likely. +On the one hand, a change in direction needs to be documented, which can be a pain. +On the other hand, a written change is easier to spot and more likely to generate complaints.

Rules

I’d argue that the path to successfully managing a research team lies in roughly the following key goals:

  • Fostering autonomy
  • Avoiding miscommunication
  • Optimizing your contribution

The remaining of the post will be a series of tasks or rules to achieve these goals.

Most of these ideas probably generalize well to collaboration outside of academia, but I hesitate to make more general claims.

Fostering automony

The tips here are aimed at avoiding supervision overhead and training future leads.

Provide a (simplified version of the) bigger picture

Try to paint the bigger picture, even for menial tasks within large projects. +For you, this may be the nth project you’re involved in this year, but the new intern may not have even heard about European projects before. +Going back to the idea of direction, it is easier to work on something if you know the context of your work.

Having a general idea of the project and the context of your task will also help you make decisions on your own. +For instance, if I am told to develop a shiny new API for text classification, I may have to ask many questions: 1) what will be input look like?; 2) what should the parameters be?; 3) am I using POST or GET requests?; 4) should I return a JSON object or an XML?… +What if, instead of that, I am also told this API will be used in the context of project X, that our organization will be the only consumers of the API, and they also give me a link to the project’s docs. +I may be able to figure out some of those answers on my own (e.g., by finding examples in the project’s website), or decide that some questions are not vital at this point (e.g., if we are the only consumers, we can change from GET to POST if we need to much more easily).

One caveat here is that a link to the documentation or some vague words about the project do not constitute proper context. +You are responsible for summarizing the important bits of the context, providing instructions on how to navigate the reference materials, and being open to answer questions that may arise in the exchange.

Do not discuss implementation details unless strictly necessary

There is a fine line between discussing a non-trivial implementation detail and bikeshedding for hours about class names and code best practices. +For that reason, you should try to prioritize discussing high-level parts of the problem and the assignment, and trust the student to figure out the details on their own, or come back to you for clarification.

It is very common that students focus on very specific details when they are sharing their progress with their supervisors. +They will generally try to start by showing snippets of code and their results. +I find it helpful to remind them to explain their problems top-to-bottom, starting with a sentence or two about the context of their project, the description and motivation of the specific task they were doing, and the relationship with previous (and future) tasks. +That usually helps figure out the level of understanding of the student, whether there are any conceptual errors, and whether the specific block or problem is really worth discussing during the meeting.

Some technical problems will warrant a discussion in detail, either due to their complexity or their importance to the project. +In those cases, always limit the time you will spend on that specific issue ahead of time, and make sure to allow for some time at the end of the meeting to go back to any important high-level details.

If there are other students that worked on similar projects, do not hesitate to refer your new student to them. +It can be an opportunity for them to collaborate, and for the original student to work on explaining and teaching technical issues.

Provide feedback

Make a point of evaluating the results of each student on every level, and provide constructive and actionable feedback to them. +Even if no technical issues arise during the project, try to review the code and give some tips (e.g., formatting, code structure, DRY). +Try to focus on bigger issues and enforcing best practices before nitpicking and giving feedback on small subjective improvements.

Make it clear when your feedback is objective/best practice (e.g., a function is deprecated) and when it is a matter of preference. +If it is the latter, try to provide more than one alternative, to encourage them to think about it and make an educated decision.

Take documentation and knowledge transfer seriously

Taking the time to write down basic documentation can save a lot of time in the long run. +Besides, most of the job of mentoring a new student is lost when that student finishes their degree and leaves to find a job in industry. +Good documentation can remain in your organization and be extended long after the intern is gone.

This is very obvious for specific tools, whether internal or public. +Good documentation means any new member can check the tool and use it without much assistance. +Even better documentation helps newcomers contribute to the tool. +Make sure to make it clear who to approach if something is missing from the documentation, and make it easier to do so than to make assumptions and use the tool incorrectly.

Writing documentation can be very time consuming, and sometimes it is hard to know exactly what things to focus on when writing the docs. +You need to anticipate the needs of the future user. +If you are short on time, a good strategy is to delegate the writing of the documentation. +Instead of going into details, you can write a very barebones version and training a new user to use and contribute to your tool. +Then, leave it up to the new user to extend the documentation, including more details and pitfalls. +As a bonus, reading and fixing the docs will give you a better sense of how well that new user understands the tool, as well as possible improvements.

This tip also applies to more general areas such as machine learning, graph neural networks, or simulation. +Just remember you do not need to reinvent the wheel in those cases. +A simple summary and a list of references to expand on the topic could be more than enough. +Make sure to also include any specifics that apply to your organization. +For instance, point to repositories on github (public or private) that can be used to explore the topic, examples of similar projects in the domain, etc.

Identify what information is important for any new hire and present it to them as clearly as possible. +Part of that information should be where and how common knowledge is stored and shared, should they need more information in the future. +Make this documentation as easy to discover and consume as possible. +Centralizing this common information in the form of a wiki is often a good idea.

Lastly, make it easy for any member of your organization to update this common documentation, and encourage them to do so. +Whenever a member asks you something useful that is not documented, don’t just answer the question. +Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer. +If your organization’s culture does not encourage using these docs, they will quickly get outdated and fall out of use.

One example of taking this documentation approach really seriously is Oxide (computer company). +They have a process they call Request For Discussion (RFD), which they use to discuss and document both technical and organizational decisions. +For instance, they have RFDs on why they record every meeting, RFDs about their choice of database, and even an meta-RFD that discusses the motivation RFDs and how the process should work.

Trust your teammate’s ability to learn

I’ve been bitten by this way too many times. +Your students are probably more capable of learning than you think, especially if you have set up your documentation right. +What they lack in experience, they make up for with free time, a (more) neuroplasticity and determination.

Sure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.

Use tools wisely

Your students probably have little experience with code versioning, reviewing processes, time management, etc. +A good choice of tools and some training can go a long way and make your life much easier in the long run. +It will also give your students a taste of what working in a bigger/real company feels like and a head start.

For instance, using git makes it easier to collaborate on code. +It also ensures that your results will not be lost if your student’s laptop gets stolen.

Using GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students. +It will force them to commit working code, and it will make it easier to check their results and discuss the end result.

Using overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results. +You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration. +In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.

Also, on a related note, make sure every team member has a proper development setup. +It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work. +It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.

Encourage cooperation

Do not become the center of every conversation. +If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.

The ability to discuss with your peers and report only when needed will be extremely important for them in the future. +They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are). +That might lead to valuable insights and improvements for your team and project.

Moreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.

Reward proactivity

The whole point of this section is to get your team to work independently when possible. +Be explicit about this goal to make sure it is clear to everyone. +And encourage behavior that aligns with this goal, even on a small scale.

For instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements. +Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time. +Do not jump straight to criticize it. +Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.

Don’t be a perfectionist

Perfect is the enemy of done. +It is also the enemy of a happy co-worker.

Try to remember that you are dealing with students, and you were probably no better at their age. +Besides, you probably delegated the task beause you did not have any spare time to do it yourself. +FIXME is often better than TODO.

Take the opportunity to provide some feedback and teach them something useful. +Some mistakes are also worth adding to your documentation, or presenting to other students in a presentation.

Avoiding miscommunication

A common source of wasted effort and unnecessary back-and-forth is miscommunication. +These are some tips to help keep everyone on the team informed and aligned.

Make priorities clear

All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks. +This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.

Define boundaries (and abstractions)

Once again, the goal is generally to achieve some sort of parallelism between your team members. +In order to do that, they need to know how they will interact with each other.

On a more general level, this means knowing the responsibilities and scope of your work.

On a more specific level, it means knowing their dependency graph. +In other words, whether the progress of one team member will depend on the results of another one. +Whenever there is a dependency, the interface should be made very clear. +This often takes the form of an API, a file with a given format, or a section of a document.

Take some time to define the boundary as precisely as needed at that point in the project. +I would suggest having specific examples that you can discuss and modify. +It is hard to discuss in the abstract, especially for inexperienced contributors. +When in doubt, default to the simplest option (e.g., a common file vs using a database). +Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there. +Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.

One type of failure I’ve seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples. +That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.

Be approachable

Did I wrote a whole section about autonomy? Yes. +Is the end goal to do more and talk less? Also yes. +Thing is, no process is perfect, and misunderstandings are bound to happen at some point. +If your only response to questions is a grumpy face or a “read the freaking docs”, your students will not alert you when something really needs your attention, and you will find out too late. +For instance, the documentation may be unclear, or your processes may be inadvertedly alienating new members and making new hires harder.

Another way to be approachable is to be clear about your shortcomings, and whether something you are saying is negotiable and/or debatable. +My rule of thumb is to err on the side of negotiable, and only be strict when it is really necessary (e.g., time constraints or an unproductive student). +We are all more likely to finish our tasks if we feel them ours, if we a say in how and when to perform them.

Just to be clear, approachable does not mean you have to be their confident or their best friend. +It also does not mean that it is okay to challenge or question you continuously. +Some times it is okay to simply say “just do as I say”.

Review frequently

One type of review is individual. +It involves reviewing code on github, or reading deliverables and papers on overleaf. +It can help catch misunderstandings, and measure the true rate of progress in the individual tasks. +The other type of review is done as a group, by going through the key progress and action points. +This type of review helps everyone stay on the same page, and catch any general drifts in the project, such as misaligned priorities.

The frequency of each type of review depends on the specific nature of the project, the types of tasks being performed by the student, and your confidence on the student’s abilities.

Optimizing your contribution

Tips on optimizing your contribution to the team.

Prioritize, prioritize, prioritize

Part of your job as a project lead is to identify the main goals in a project and to prioritize the tasks that will lead you there. +On the other hand, you are part of a research group, and you should be actively involved in its health and future. +Lastly, you are also in charge of the life-long project that is your research career.

In all these cases, your goal should be to identify the long term goals, come up with a sound strategy, and prioritize the tasks that will lead you and your group there. +Keeping your priorities straight will help you make steady progress, and avoid bikeshedding and changing goalposts. +It will also help you steer your progress in the right direction, since we all have limited time and effort and can’t do everything at once.

The fact that your time is limited also means that you will need to decide how to prioritize these three roles. +I’ve listed them in increasing level of importance for me. +It means that it is okay to focus on a specific project for a while, but if progress in your career is stalled - usually through publication - you need to reevaluate and concentrate your efforts on that.

Be okay with (short-term) inefficiencies

I’ve personally struggled with delegating tasks that will take me orders of magnitude less work than they will a student. +Thing is, most tasks will fall under this category, and your time is limited, so you have to delegate if you want to have time for more important matters. +If you never delegate any tasks, you are not allowing your team to learn and catch up on whatever technical skills are required. +Besides, you are not improving yourself on the managerial side of things. +It turns out delegating is hard, it requires a whole set of non-technical skills. +I suspect this is oftentimes the reason we don’t do delegate in the first place: delegating is hard, and technical tasks are usually more straightforward, so we just don’t want to do the work.

Don’t neglect training

You are a senior researcher. +You probably know how to solve problems in your domain quite efficiently. +In my case, that means processing data and developing code.

That means I could dedicate my days to processing data and developing new code for my group. +That group would likely be used in multiple projects. +However, there is a hard limit to how much code I can push out in a day, especially if you take into account other obligations such as teaching.

A wiser strategy would be to set aside some of that coding time to instead help students become better programmers. +Firstly, because those students will be thankful and more motivated to work than when they are left to learn on their own without much guidance. +Secondly, because those students will then be more prepared to help me out if I delegate a task to them. +And, lastly, because these students have a whole life in fron of them. +A life full of big projects of their own, and contributions to society. +That little training time can have a compounding effect in the future.

Set a time limit for your interactions in advance

Really long slots can easily lead to bikeshedding and going unnecessarily deep into implementation details, which is clearly an inefficient use of your time. +Even worse, our attention span and memory are finite, so longer and dense meetings can lead to fatigue and to missing or dilluting important points in the conversation.

For these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary. +You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.

Beyond your team

The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control. +But teams rarely work in isolation, you will most likely +In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization’s culture and sense of belonging.

Many of the aspects I talked about in the team section apply here. +For instance, the obsession with documentation can - and should - be applied organization-wise. +The same goes for defining boundaries and using concrete examples when collaborating with other teams. +For most intents and purposes, you can treat other teams as another contributor to your team. +Just one that will be more costly and slow to interact with.

If possible, I’d try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved. +Avoid involving whole teams in discussions when the broad strokes have not been defined yet. +The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.

On the organization’s side, I would suggest having an honest conversation about your core principles. +I really liked Bryan Cantrill’s talk about principles of technology leadership. +He goes deep into the effects that principles have had on well known companies, and how to go about defining your company’s principles. +I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.

More generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive. +And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier. +Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/emacs-show-plain-text-version/index.html b/p/emacs-show-plain-text-version/index.html new file mode 100644 index 0000000..3934f92 --- /dev/null +++ b/p/emacs-show-plain-text-version/index.html @@ -0,0 +1,28 @@ +Emacs: show plain text version +

Emacs: show plain text version

+
1
+
(font-lock-mode)
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/fixing-hdmi-flickering/index.html b/p/fixing-hdmi-flickering/index.html new file mode 100644 index 0000000..ae88e9a --- /dev/null +++ b/p/fixing-hdmi-flickering/index.html @@ -0,0 +1,34 @@ +Fixing HDMI flickering +
Featured image of post Fixing HDMI flickering

Fixing HDMI flickering

Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.

1
+2
+3
+4
+5
+6
+

+   hdmi_drive=2
+   hdmi_group=2
+   hdmi_mode=42
+   disable_overscan=1
+   config_hdmi_boost=7
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/kanata-advanced-keyboard-configuration/index.html b/p/kanata-advanced-keyboard-configuration/index.html new file mode 100644 index 0000000..7ad7e47 --- /dev/null +++ b/p/kanata-advanced-keyboard-configuration/index.html @@ -0,0 +1,198 @@ +Kanata: advanced keyboard configuration +

Kanata: advanced keyboard configuration

+
+

Kanata is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you’re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.

The project was inspired by the more popular KMonad, and the author cites some of the differences. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +Here’s a very complete config that serves as documentation.

One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.

Keyd is another alternative with a more declarative configuration format, which might lend itself to smaller.

For now I’m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren’t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that’s the idea. +We’ll see if I like it enough to stick with it.

For now, here’s my very simple config:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+
(defcfg
+  ;; Your keyboard device will likely differ from this.
+  linux-dev  /dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse
+
+  ;; Windows doesn't need any input/output configuration entries; however, there
+  ;; must still be a defcfg entry. You can keep the linux-dev entry or delete
+  ;; it and leave it empty.
+)
+
+(defsrc
+  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
+  tab  q    w    e    r    t    y    u    i    o    p    [    ]    
+  caps a    s    d    f    g    h    j    k    l    ;    '    ret
+  lsft \ z    x    c    v    b    n    m    ,    .    /    rsft
+  lctl lmet lalt           spc            ralt rmet rctl
+)
+
+
+(deflayer qwerty
+  grv  _    _    _    _    _    _    _    _    _    _    _    _    _
+  _  _    @warrows    _    _    _    _    _    _    _    _    _    _    
+  lctrl     @alctrl    @slsft    @dlalt    @flmet    _    _    @jrmet    @kralt    @lrsft    @;rctrl    _ _
+  _ _ _    _    _    _    _    _    _    _    _    _    _
+  _ _ _           @smartspace            _ _ _
+)
+
+(deflayer arrows
+  _  _    _    _    _    _    _    _    _    _    _    _    _    _
+  _  _    _    _    _    _    _    _    _    _    _    _    _    
+  _     _    _    _    @flmet    _    left down up rght _ _ _
+  _ _ _    _    _    _    _    _    _    _    _    _    _
+  _ _ _           @smartspace            _ _ _
+)
+
+(deflayer colemak
+  grv XX    XX    XX    XX    XX    XX    XX    XX    XX    XX    XX    XX    _
+  tab         q       w       f       p       b       j       l       u       y       ;       [       ]    
+  lctrl   @alctrl @rlsft      @slalt      @tlmet      g       m       @nrmet      @eralt      @irsft      @orctrl      '       ret
+  lsft    XX z       x       c       d       v     k       h       ,       .       /       rsft
+  XX XX XX           @smartspace            XX XX XX
+)
+
+(deflayer magic
+  _  @clmk    @qwerty    _    _    _    _    _    _    _    _    _    _    _
+  _  _    _    _    _    _    _    _    _    _    _    _    _    
+  _     tab    A-tab    _    _    _    _    bspc  esc   _ ret    _  _
+  _ _ _    _    _    _    _    _    _    _    _    _    _
+  _ _ _           _            _ _ _
+)
+
+(defalias
+  warrows (tap-hold 200 200 w (layer-toggle arrows))
+
+  alctrl (tap-hold 200 200 a lctrl)
+  slsft (tap-hold 200 200 s lsft)
+  dlalt (tap-hold 200 200 d lalt)
+  flmet (tap-hold 200 200 f lmet)
+
+  jrmet (tap-hold 200 200 j rmet)
+  kralt (tap-hold 200 200 k ralt)
+  lrsft (tap-hold 200 200 l rsft)
+  ;rctrl (tap-hold 200 200 ; rctrl)
+
+  rlsft (tap-hold 200 200 r lsft)
+  slalt (tap-hold 200 200 s lalt)
+  tlmet (tap-hold 200 200 t lmet)
+  nrmet (tap-hold 200 200 n rmet)
+  eralt (tap-hold 200 200 e ralt)
+  irsft (tap-hold 200 200 i rsft)
+  orctrl (tap-hold 200 200 o rctrl)
+  clmk (layer-switch colemak)
+  qwerty (layer-switch qwerty)
+
+  smartspace (tap-dance 200 (
+      (tap-hold 300 300 spc (layer-toggle magic))
+      (tap-hold 300 300 (one-shot 300 lalt) spc)
+      a
+      ))
+
+  )
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/linux-on-the-microsoft-surface-go/index.html b/p/linux-on-the-microsoft-surface-go/index.html new file mode 100644 index 0000000..b03a234 --- /dev/null +++ b/p/linux-on-the-microsoft-surface-go/index.html @@ -0,0 +1,60 @@ +Linux on the Microsoft Surface Go +

Linux on the Microsoft Surface Go

+
+

Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. +These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.

Installing the kernel

1
+2
+
git clone --depth 1 https://github.com/jakeday/linux-surface.git ~/linux-surface
+cp -a ~/linux-surface /media/<your usb>
+
1
+2
+3
+
cp -a /media/<your usb>/linux-surface ~/
+cd ~/linux-surface/
+sudo sh setup.sh
+

Booting ubuntu first

Switch out of Windows S mode.

Boot into the “Command Prompt”.

From Windows go to “change advanced startup options” and select “restart now”.

When it reboots, choose the “Troubleshoot” option, then choose the “Advanced options” option, and finally choose the “Command Prompt” option.

After the device reboots, login to the command prompt and then you should see a terminal with X:\windows\system32>

At the prompt, check your UEFI entries:

1
+
bcdedit /enum firmware
+

Copy UEFI entry of “Windows Boot Manager” to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d “Ubuntu”

Copy the printed GUID number including the braces {} using Ctrl+C

Set file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \EFI\ubuntu\grubx64.efi

Set Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.

1
+
bcdedit /set {fwbootmgr} displayorder {guid} /addfirst
+

Check your UEFI entries again: bcdedit /enum firmware You should see something like this:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+

+Firmware Boot Manager
+---------------------
+identifier              {fwbootmgr}
+displayorder            {3510232e-f8eb-e811-95ce-9ecab3f9d1c4}
+                        {bootmgr}
+                        {2148799b-f8eb-e811-95ce-9ecab3f9d1c4}
+                        {312e8a67-c2f6-e811-95ce-3c1ab3f9d1de}
+                        {312e8a68-c2f6-e811-95ce-3c1ab3f9d1de}
+timeout                 0
+

Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/logitech-mb850-combi-in-linux/index.html b/p/logitech-mb850-combi-in-linux/index.html new file mode 100644 index 0000000..a06ee99 --- /dev/null +++ b/p/logitech-mb850-combi-in-linux/index.html @@ -0,0 +1,44 @@ +Logitech MB850 combi in linux +

Logitech MB850 combi in linux

+
+

As a follow-up to my last post, I’ve decided to also configure my mk850 combo (k850 + m720 triathlon).

Some notes:

  • The keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc)
  • The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I’ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM.
1
+2
+3
+4
+5
+6
+7
+
evdev:input:*                                                        
+ KEYBOARD_KEY_70039=leftctrl  # bind capslock to w 
+
+evdev:input:b0005v046DpB015*
+ KEYBOARD_KEY_700e0=f19
+ KEYBOARD_KEY_700e2=unknown
+ KEYBOARD_KEY_7002b=unknown
+

After that, simply run:

1
+
 sudo udevadm hwdb --update && sudo udevadm trigger
+

Make sure the settings have been applied by running evemu-describe:

1
+
 sudo /sbin/evemu-describe  /dev/input/event<id of your device> | grep KEY_  
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/logitech-mx-keys-for-mac-on-linux/index.html b/p/logitech-mx-keys-for-mac-on-linux/index.html new file mode 100644 index 0000000..0744920 --- /dev/null +++ b/p/logitech-mx-keys-for-mac-on-linux/index.html @@ -0,0 +1,56 @@ +Logitech MX Keys for Mac on Linux +

Logitech MX Keys for Mac on Linux

+
+

I recently got Logitech MX Keys for Mac keyboard at work. +The German version, to be more precise. +This version was three times cheaper than the Windows equivalent with either US or ES layout. +Since I touch type anyway, I thought it was a bargain.

As soon as I plugged it in, I realized there were some glaring issues with the keyboard. +First of all, the Meta/Super and Alt keys are reversed in this keyboard. +In the normal/full version of this keyboard, Logitech gives an option to choose between Mac, Windows and iOS host, and that changes the behavior of the keys. +In this version, tho, only iOS and Mac are available.

Besides that, there’s the issue of the grave (tilde) and angle keys switched as well.

Switching these keys around would be very easy with Xorg, but Wayland once again complicates things…

These issues almost made me return the keyboard. +Luckily, tho, there is another option: configuring the keys one level lower than wayland (and X11), through hwdb.

Long story short, this will configure any Logitech keyboard with the same product id (0x4092) to use a saner configuration:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
#File: /etc/udev/hwdb.d/90-logitech-keyboard.hwdb
+
+evdev:input:b0003v046Dp4092*
+ KEYBOARD_KEY_700e2=leftmeta
+ KEYBOARD_KEY_700e3=leftalt
+ KEYBOARD_KEY_70039=leftctrl
+ KEYBOARD_KEY_70064=102nd
+ KEYBOARD_KEY_70035=grave
+ KEYBOARD_KEY_700e7=rightalt
+ KEYBOARD_KEY_700e6=rightmeta
+ KEYBOARD_KEY_7006d=compose
+

After that, simply run:

1
+
 sudo udevadm hwdb --update && sudo udevadm trigger
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/nix-recipe-for-python-projects/index.html b/p/nix-recipe-for-python-projects/index.html new file mode 100644 index 0000000..cce1655 --- /dev/null +++ b/p/nix-recipe-for-python-projects/index.html @@ -0,0 +1,106 @@ +Nix Recipe for Python Projects +

Nix Recipe for Python Projects

+
+

This is a quick and easy recipe to add a default.nix to any Python project with a requirements.txt file:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+
with import <nixpkgs> { };
+
+let
+  pythonPackages = python311Packages;
+in pkgs.mkShell rec {
+  name = "impurePythonEnv";
+  venvDir = "./.venv";
+  buildInputs = [
+    # A python interpreter including the 'venv' module is required to bootstrap
+    # the environment.
+    pythonPackages.python
+
+    # This execute some shell code to initialize a venv in $venvDir before
+    # dropping into the shell
+    pythonPackages.venvShellHook
+
+    # Those are dependencies that we would like to use from nixpkgs, which will
+    # add them to PYTHONPATH and thus make them accessible from within the venv.
+    pythonPackages.numpy
+    pythonPackages.requests
+
+    # In this particular example, in order to compile any binary extensions they may
+    # require, the python modules listed in the hypothetical requirements.txt need
+    # the following packages to be installed locally:
+    taglib
+    openssl
+    git
+    libxml2
+    libxslt
+    libzip
+    zlib
+  ];
+
+  # Now we can execute any commands within the virtual environment.
+  # This is optional and can be left out to run pip manually.
+  postShellHook = ''
+    pip install -r requirements.txt
+  '';
+
+}
+

Now, you will get a clean environment by running:

1
+
nix-shell
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/progress-bars-in-python/index.html b/p/progress-bars-in-python/index.html new file mode 100644 index 0000000..9d3962d --- /dev/null +++ b/p/progress-bars-in-python/index.html @@ -0,0 +1,34 @@ +Progress bars in python +

Progress bars in python

+
+

tqdm is a nice way to add progress +bars in the command line or in a jupyter notebook.

image

1
+2
+3
+4
+5
+
from tqdm import tqdm
+import time
+
+for i in tqdm(range(100)):
+    time.sleep(1)
+
+Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/proxies-with-apache-and-python/index.html b/p/proxies-with-apache-and-python/index.html new file mode 100644 index 0000000..ccc101e --- /dev/null +++ b/p/proxies-with-apache-and-python/index.html @@ -0,0 +1,92 @@ +Proxies with Apache and python +

Proxies with Apache and python

+
+

This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name application. Hence, wsgi:application.

Gunicorn

1
+2
+3
+4
+5
+
<Location /myapp/>
+    ProxyPass http://127.0.0.1:8888/myapp/
+    ProxyPassReverse http://127.0.0.1:8888/myapp/
+    RequestHeader set SCRIPT_NAME "/myapp/"
+</Location>
+

Important: SCRIPT_NAME and the end of ProxyPass URL MUST BE +THE SAME. Otherwise, Gunicorn will fail miserably.

Try it with:

1
+
venv/bin/gunicorn -w 4 -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application
+

UWSGI

This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).

1
+2
+3
+4
+
<Location /myapp/>
+    SetHandler uwsgi_handler
+    uWSGISocker 127.0.0.1:8888
+</Location>
+

Try it with:

1
+
uwsgi --socket 127.0.0.1:8888 -w wsgi:application
+

Extra: Supervisor

If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
[unix_http_server]
+file=/tmp/myapp.sock ; path to your socket file
+
+[supervisord]
+logfile = %(here)s/logs/supervisor.log
+childlogdir = %(here)s/logs/
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[supervisorctl]
+logfile = %(here)s/logs/supervisorctl.log
+serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
+
+[program:myapp]
+command = venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application
+directory = %(here)s
+environment = PATH=%(here)s/venv/bin/
+logfile = %(here)s/logs/myapp.log
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/publishing-on-pypi/index.html b/p/publishing-on-pypi/index.html new file mode 100644 index 0000000..c838191 --- /dev/null +++ b/p/publishing-on-pypi/index.html @@ -0,0 +1,126 @@ +Publishing on PyPi +

Publishing on PyPi

+
+

Developing a python module and publishing it on Github is cool, but most +of the times you want others to download and use it easily. That is the +role of PyPi, the python package repository. In this post I show you how +to publish your package in less than 10 minutes.

Choose a fancy name

If you haven’t done so yet, take a minute or two to think about this. +To publish on PyPi you need a name for your package that isn’t taken. +What’s more, a catchy and unique name will help people remember your +module and feel more inclined to at least try it.

The package name should hint what your module does, but that’s not +always the case. That’s your call. I personally put uniqueness and +memorability over describing the functionality.

Create a .pypirc configuration file

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+
[distutils] # this tells distutils what package indexes you can push to
+index-servers =
+    pypi # the live PyPI
+    pypitest # test PyPI
+
+[pypi] # authentication details for live PyPI
+repository = https://pypi.python.org/pypi
+username = { your_username }
+password = { your_password } # not necessary
+
+[pypitest] # authentication details for test PyPI
+repository = https://testpypi.python.org/pypi
+username = { your_username }
+

As you can see, you need to register both in the main pypi +repository and +the testing +server. The +usernames and passwords might be different, that is up to you!

Prepare your package

This should be the structure:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+
root-dir/    # Any name you want
+    setup.py
+    setup.cfg
+    LICENSE.txt
+    README.md
+    mypackage/
+        __init__.py
+        foo.py
+        bar.py
+        baz.py
+

setup.cfg

1
+2
+
[metadata]
+description-file = README.md
+

The markdown README is the de facto standard in Github, but you can +also use rST (reStructuredText), the standard in the python community.

setup.py

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
from distutils.core import setup
+
+setup(name = 'mypackage',
+      packages = ['mypackage'], # this must be the same as the name above
+      version = '{ version }',
+      description = '{ description }',
+      author = '{ name }',
+      email = '{ email }',
+      url = 'https://github.com/{user}/{package}', # URL to the github repo
+      download_url = 'https://github.com/{user}/{repo}/tarball/{version}',
+      keywords = ['websockets', 'display', 'd3'], # list of keywords that represent your package
+      classifiers = [], )
+

You might notice that the download_url points to a Github URL. We could +host our package anywhere, but Github is a convenient option. To create +the tarball and the zip packages, you only need to tag a tag in your +repository and push it to github:

1
+2
+
git tag {version} -m "{ Description of this tag/version}"
+git push --tags origin master
+

Push to the testing/main pypi server

It is advisable that you try your package on the test repository and fix +any problems first. The process is simple:

1
+
python setup.py register -r {pypitest/pypi} python setup.py sdist upload -r {pypitest/pypi}
+

If everything went as expected, you can now install your package through +pip and browse your package’s page. For instance, check my senpy +package: https://pypi.python.org/pypi/senpy

1
+
pip install senpy
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/rdf-is-dead/index.html b/p/rdf-is-dead/index.html new file mode 100644 index 0000000..4576e70 --- /dev/null +++ b/p/rdf-is-dead/index.html @@ -0,0 +1,26 @@ +RDF Is Dead +

RDF Is Dead

+
+

A big part of my research has been around vocabularies and semantic annotation. +And, to be honest, I’ve grown increasingly dissatisfied with the field. +To the point where I dread having to work on it. +Some day I will write about it in length, but today I’ve stumbled upon a post that covers the topic quite well: The Semantic Web is Dead - Long Live the Semantic Web (styling mine).

In particular, this section has really resonated with me:

Academics and Industry

The political economy of academia and its interaction with industry is the origin of our current lack of a functional Semantic Web.

Academia is structured in a way that there is very little incentive for anyone to build usable software. Instead, you are elevated for rapidly throwing together an idea, a tiny proof of concept, and to iterate on microscopic variations of this thing to produce as many papers as possible.

In engineering, the devil is in the detail. You really need to get into the weeds before you can know what the right thing to do is. This is simultaneously a devastating situation for industry and academia. Nobody is going to wait around for a team of engineers to finish building a system to write about it in Academia. You’ll be passed immediately by legions of paper pushers. And in industry, you can’t just be mucking about with a system that you might have to throw away.

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/remove-git-files-with-globbing/index.html b/p/remove-git-files-with-globbing/index.html new file mode 100644 index 0000000..c6de615 --- /dev/null +++ b/p/remove-git-files-with-globbing/index.html @@ -0,0 +1,26 @@ +Remove git files with globbing +

Remove git files with globbing

+
+

A simple trick. If you want to remove all the ‘.swp’ files from a git +repository, just use:

1
+
git rm --cached '**.swp'
+
+Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/sharing-dotfiles/index.html b/p/sharing-dotfiles/index.html new file mode 100644 index 0000000..42db2c4 --- /dev/null +++ b/p/sharing-dotfiles/index.html @@ -0,0 +1,44 @@ +Sharing dotfiles +

Sharing dotfiles

+
+

Today’s post is half a quick note, half public shaming. In other words, +it is a reminder to be very careful with OAuth tokens and passwords.

As part of moving to emacs, I starting using the incredibly useful +gh.el. When you first use it, the +extension saves either your password or an OAuth token in your +.gitconfig file. This is cool and convenient, unless you happen to be +publishing your .gitconfig file in a public +repo.

So, how can you still share your gitconfig without sharing your +password/token with the rest of the world? Since Git 1.7.0, you can +include other files in your +gitconfig.

1
+2
+
[include]
+    path = ~/.gitconfig_secret
+

And now, in your .gitconfig_secret file, you just have to add this:

1
+2
+3
+
[github]
+    user = balkian 
+    token = "< Your secret token >" 
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/updating-eurolovemap/index.html b/p/updating-eurolovemap/index.html new file mode 100644 index 0000000..08e45ef --- /dev/null +++ b/p/updating-eurolovemap/index.html @@ -0,0 +1,99 @@ +Updating EuroLoveMap +

Updating EuroLoveMap

+
+

As part of the OpeNER +hackathon +we decided to build a prototype that would allow us to compare how +different countries feel about several topics. We used the OpeNER +pipeline to get the sentiment from a set of newspaper articles we +gathered from media in several languages. Then we aggregated those +articles by category and country (using the source of the article or the +language it was written in), obtaining the “overall feeling” of each +country about each topic. Then, we used some fancy JavaScript to make +sense out of the raw information.

It didn’t go too bad, it turns out we +won.

Now, it was time for a face-lift. I used this opportunity to play with +new technologies and improve it:

  • Using Flask, this time using python 3.3 and Bootstrap 3.0
  • Cool HTML5+JS cards (thanks to +pastetophone)
  • Automatic generation of fake personal data to test the interface
  • Obfuscation of personal emails

The result can be seen here.

Publishing a Python 3 app on Heroku

1
+
mkvirtualenv -p /usr/bin/python3.3 eurolovemap
+

Since Heroku uses python 2.7 by default, we have to tell it which +version we want, although it supports python 3.4 as well. I couldn’t +get python 3.4 working using the +deadsnakes ppa, so +I used python 3.3 instead, which works fine but is not officially +supported. Just create a file named runtime.txt in your project root, +with the python version you want to use:

1
+
python-3.3.1
+

Don’t forget to freeze your dependencies so Heroku can install them: +bash pip freze > requirements.txt

Publishing personal emails

There are really sophisticated and effective ways to obfuscate personal +emails so that spammers cannot easily grab yours. However, this time I +needed something really simple to hide our emails from the simplest form +of crawlers. Most of the team are in academia somehow, so in the end all +our emails are available in sites like Google Scholar. Anyway, nobody +likes getting spammed so I settled for a custom Caesar +cipher. Please, don’t use +it for any serious application if you are concerned about being spammed.

1
+2
+
def blur_email(email):
+    return "".join([chr(ord(i)+5) for i in email])
+

And this is the client side:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
window.onload = function(){
+     elems = document.getElementsByClassName('profile-email');
+     for(var e in elems){
+        var blur = elems[e].innerHTML;
+        var email = "";
+        for(var s in blur){
+            var a = blur.charCodeAt(s)
+            email = email+String.fromCharCode(a-5);
+        }
+        elems[e].innerHTML = email;
+     }
+}
+

Unfortunately, this approach does not hide your email from anyone using +PhantomJS, +ZombieJS or similar. For that, other +approaches like generating a picture with the address would be +necessary. Nevertheless, it is overkill for a really simple ad-hoc +application with custom formatting and just a bunch of emails that would +easily be grabbed manually.

Generation of fake data

To test the contact section of the site, I wanted to populate it with +fake data. Fake-Factory is an amazing +library that can generate fake data of almost any kind: emails, +association names, acronyms… It even lets you localise the results +(get Spanish names, for instance) and generate factories for certain +classes (à la Django).

But I also wanted pictures, enter Lorem Pixel. +With its API you can generate pictures of almost any size, for different +topics (e.g. nightlife, people) and with a custom text. You can even use +an index, so it will always show the same picture.

For instance, the picture below is served through Lorem Pixel.

By the way, if you only want cat pictures, take a look at +Placekitten. And for NSFW text, there’s the +Samuel L. Jackson Ipsum

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/uv-one-rust-tool-to-rule-all-pythons/index.html b/p/uv-one-rust-tool-to-rule-all-pythons/index.html new file mode 100644 index 0000000..e9eb20e --- /dev/null +++ b/p/uv-one-rust-tool-to-rule-all-pythons/index.html @@ -0,0 +1,93 @@ +uv - One rust tool to rule all pythons +
Featured image of post uv - One rust tool to rule all pythons

uv - One rust tool to rule all pythons

+
+

Long story short: I’m now using uv, and so should you. +It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.

Context

For years, my strategy to manage python projects has been a mix of a custom setup.py, several hand-crafted requirements.txt files (through pip freeze), a custom virtualenv per project, and multiple tools to upload to PyPI. +Although this works, this setup has many drawbacks:

  • It requires user intervention (creating a venv, sourcing it, handling new deps). This isn’t ideal if you want new (probably inexperienced) users to use your projects.
  • On a similar note, the whole process needs to be well documented if you want other users to contribute or maintain the code.
  • Pinning dependency versions is finicky, and I’ve run into problems beause of that.
  • Creating a new project involves a template, or copying files from an older project.

Of course, this is nothing new. +There is a whole site dedicated to packaging your Python project. +A plethora of different projects have come and go, with varying degrees of success.

Alternatives (poetry)

About a year before trying uv, I tried to catch up with the ecosystem and get to know the blessed new way. +However, the task proved to be a little more difficult, as the landscape is filled with a myriad of alternatives, each with their own set of drawbacks and detractors. +Packaging has historically been a weak spot, in ironical contradiction to the Zen of Python’s “There should be one– and preferably only one –obvious way to do it”,

I eventually settled on poetry. +Mostly because it seemed like the most popular alternative.

There are many things I liked about it. +First of all, having a convention for dependencies (pyproject.toml) and a tool that properly handles them was nice. +It also removed the need to remember specific incantations to build and publish my Python projects. +Lastly, I mixed it poetry2nix to create reproducible python environments using nix. +This makes for a very powerful experience.

However, there were multiple hiccups. +First of all, it took me some time to figure out which specific fields to use (each tool can define ad-hoc properties in a the pyproject.toml file), and some of them seemed redundant with the more generic ones. +Full disclosure, this specific point might be a mistake on my side, and I do not remember the details. +The second one is speed. +(Re-)creating an environment took a non-negligible amount of time.

Enter light uv

According to its repository, uv can replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +Not only that, but it also claims to do that 10-100 times faster than pip. +I must admit that it being written in rust was a another selling point for me, as I’m looking for excuses to collaborate in a decently-sized rust projejct.

Installing it is dead simple: simply download the binary (e.g., with curl) or run pip install uv. +You won’t need much more: uv seems to just do the right thing out of the box. +And it does it really, really fast. +The rest of the time it gets out of the way.

My only gripe so far is that I don’t seem to find a built-in command to drop into a shell, but that is nothing that uv run $SHELL cannot fix.

Common operations

Initialize a repository

1
+
uv init
+

Adding dependencies

1
+
uv add senpy
+

Running commands inside the environment

1
+2
+3
+4
+
uv run <COMMAND>
+
+# e.g., run a shell using your python version and dependencies
+uv run $SHELL
+

Dependency tree

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
uv shell
+Resolved 44 packages in 1ms                                            
+my-project v0.1.0                                             
+├── fastapi[standard] v0.115.8                                         
+│   ├── pydantic v2.10.6                                               
+│   │   ├── annotated-types v0.7.0                                     
+│   │   ├── pydantic-core v2.27.2                                      
+│   │   │   └── typing-extensions v4.12.2                              
+│   │   └── typing-extensions v4.12.2                                  
+│   ├── starlette v0.45.3                                              
+│   │   └── anyio v4.8.0                                               
+│   │       ├── exceptiongroup v1.2.2                                  
+│   │       ├── idna v3.10                                             
+│   │       ├── sniffio v1.3.1                                         
+│   │       └── typing-extensions v4.12.2                              
+│   ├── typing-extensions v4.12.2                                      
+│   ├── email-validator v2.2.0 (extra: standard)                       
+│   │   ├── dnspython v2.7.0
+...
+
+Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/p/zotero/index.html b/p/zotero/index.html new file mode 100644 index 0000000..828b0c8 --- /dev/null +++ b/p/zotero/index.html @@ -0,0 +1,104 @@ +Zotero +

Zotero

+
+

Zotero is an Open Source tool that lets you +organise your bibliography, syncing it with the cloud. Unlike other +alternatives such as Mendeley, Zotero can +upload the attachments and data to a private cloud via WebDav.

If you use nginx as your web server, know that even though it provides +partial support for webdav, Zotero needs more than that. Hence, you will +need another webdav server, and optionally let nginx proxy to it. This +short post provides the basics to get that set-up working under +Debian/Ubuntu.

Setting up Apache

First we need to install Apache:

1
+
sudo apt-get install apache2
+

Change the head of “/etc/apache2/sites-enabled/000-default” to:

1
+
<VirtualHost *:880>
+

Then, create a file /etc/apache2/sites-available/webdav:

 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+
Alias /dav /home/webdav/dav
+<Location /dav>
+    Dav on
+    Order Allow,Deny
+    Allow from all
+    Dav On
+    Options +Indexes
+    AuthType Basic
+    AuthName DAV
+    AuthBasicProvider file
+    AuthUserFile /home/webdav/.htpasswd
+    Require valid-user
+</Location>
+

Ideally, you want your webdav folders to be private, adding +authentication to them. So you need to create the webdav and zotero +users and add the passwords to an htpasswd file. Even though you could +use a single user, since you will be configuring several clients with +your credentials I encourage you to create the zotero user as well. This +way you can always change the password for zotero without affecting any +other application using webdav.

1
+2
+3
+4
+
sudo adduser webdav
+sudo htpasswd -c /home/webdav/.htpasswd webdav
+sudo htpasswd /home/webdav/.htpasswd zotero
+sudo mkdir -p /home/webdav/dav/zotero
+

Enable the site and restart apache:

1
+2
+3
+4
+
sudo a2enmod webdav
+sudo a2enmod dav_fs
+sudo a2ensite webdav
+sudo service apache2 restart
+

At this point everything should be working at +http://<your_host>:880/dav/zotero

Setting up NGINX

After the Apache side is working, we can use nginx as a proxy to get +cleaner URIs. In your desired site/location, add this:

1
+2
+3
+4
+5
+6
+7
+
location /dav {
+    client_max_body_size 20M;
+    proxy_set_header X-Real-IP  $remote_addr;
+    proxy_set_header X-Forwarded-For $remote_addr;
+    proxy_set_header Host $host;
+    proxy_pass http://127.0.0.1:880;
+}
+

Now just reload nginx:

1
+
sudo service nginx force-reload
+

Extras

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/page/1/index.html b/page/1/index.html new file mode 100644 index 0000000..19a2ffe --- /dev/null +++ b/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/ + \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 0000000..7f8aa76 --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,46 @@ +Pager 2 - J. Fernando Sánchez +
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 0000000..fe3de0e --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,46 @@ +Pager 3 - J. Fernando Sánchez +

Zotero

+
+
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 0000000..d4b1ff1 --- /dev/null +++ b/page/4/index.html @@ -0,0 +1,43 @@ +Pager 4 - J. Fernando Sánchez +
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/page/index.html b/page/index.html new file mode 100644 index 0000000..f492be1 --- /dev/null +++ b/page/index.html @@ -0,0 +1,33 @@ +Pages +

Section

5 pages

Pages

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/page/index.xml b/page/index.xml new file mode 100644 index 0000000..59d2519 --- /dev/null +++ b/page/index.xml @@ -0,0 +1,47 @@ +Pages on J. Fernando Sánchezhttps://balkian.com/page/Recent content in Pages on J. Fernando SánchezHugo -- gohugo.ioen-usSun, 06 Mar 2022 00:00:00 +0000Archiveshttps://balkian.com/archives/Sun, 06 Mar 2022 00:00:00 +0000https://balkian.com/archives/Abouthttps://balkian.com/about/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/about/<p>Hello there, stranger! &#x1f44b;</p> +<h2 id="about-me">About me +</h2><p>My name is Fernando, and I like learning and solving hard problems. +Especially when it comes to computers, engineering and languages.</p> +<p>I currently work at the Technical University of Madrid (UPM) as an assistant professor in the school of Telecommunications Engineering. +You can check out my <a class="link" href="https://balkian.com/page/projects" >previous projects</a>, and <a class="link" href="https://scholar.google.com/citations?user=JLNusZ8AAAAJ&amp;hl=en" target="_blank" rel="noopener" +>my publications</a>. +Feel free to get in touch through the comment section, an e-mail (<code>my first initial</code> <code>@sanchezrada.es</code>) or any other platform. +I am always happy to help and collaborate.</p> +<h2 id="about-this-blog">About this blog +</h2><p>I use this blog for future reference, to write down some of the lessons I learn so. +I also see it as an exercise in reflection and sorting out my ideas. +Although I mostly do this for myself, to keep some lasting notes for the future, I also do it in hopes it might help someone like me in the future.</p> +<p>Each post is an independent note. +To keep some structure I will try to stick to general categories (e.g., programming, project management, linux), and add meaningful tags to help you and me find this information in the future. +You may also use the <a class="link" href="https://balkian.com/search" >search bar</a> if you are looking for something specific and wondering if I&rsquo;ve covered it. +For short thematically connected snippets and tips, I keep a dedicated section with <a class="link" href="https://balkian.com/cheatsheet" >Cheatsheets</a>.</p>Linkshttps://balkian.com/links/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/links/Projectshttps://balkian.com/projects/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/projects/<h1 id="ongoing-projects">Ongoing Projects +</h1><ul> +<li><a class="link" href="https://soilsim.readthedocs.io" target="_blank" rel="noopener" +>Soil</a>: an agent-based simulator for social networks based on nx-sim and networkx.</li> +<li><a class="link" href="https://github.com/balkian/soilent" target="_blank" rel="noopener" +>Soilent</a>: an efficient scheduler for soil using rust and pyo3.</li> +<li><a class="link" href="https://senpy.readthedocs.io" target="_blank" rel="noopener" +>Senpy</a>: a framework for semantic sentiment and emotion analysis services.</li> +</ul> +<h1 id="past-projects">Past Projects +</h1><ul> +<li><a class="link" href="http://gsi.dit.upm.es/ontologies/onyx" target="_blank" rel="noopener" +>Onyx</a>: an ontology for emotion analysis that includes concepts from W3C&rsquo;s provenance.</li> +<li><a class="link" href="https://github.com/balkian/ESP8266_Clock_NTP" target="_blank" rel="noopener" +>ESP8266 Clock NTP</a>: a simple clock display using arduino, the ESP8266 and NTP (network time protocol).</li> +<li><a class="link" href="https://github.com/balkian/shinesp" target="_blank" rel="noopener" +>Shine ESP</a>: control an ws2812b LED strip over the network with an ESP8266.</li> +<li><a class="link" href="https://github.com/balkian/bitter" target="_blank" rel="noopener" +>Bitter</a>: a wrapper and CLI over the (now defunct) Twitter API to researchers to download Twitter data much faster using multiple accounts.</li> +<li><a class="link" href="http://gsi.dit.upm.es/ontologies/marl" target="_blank" rel="noopener" +>Marl</a>: I updated this ontology, originally created by Adam Westerski, to make it compatible with the W3C&rsquo;s provenance ontology.</li> +<li><a class="link" href="http://github.com/balkian/hermes" target="_blank" rel="noopener" +>Hermes</a>: one of my first projects, developed together with David Pérez as the special custom assignment in one of our courses. Hermes is an affective bot designed to mimic the behavour of humans. It included a plug-in system for its sensors and actuators. The information from its sensors changed its emotional state, which was shown via its actuators. Among others, it could fetch inforation from Twitter or its host system and change the expressions of an external Face made with servo motors or speak via its Text-To-Speech software. For instance, it could detect it was running out of battery, showing a sad face and sending an alerting tweet. You can see it in action in these two youtube videos: <a class="link" href="http://www.youtube.com/watch?v=KnEYahPD9z4" target="_blank" rel="noopener" +>Part 1</a> and <a class="link" href="http://www.youtube.com/watch?v=lQZldCTPEJc" target="_blank" rel="noopener" +>Part 2</a>.</li> +<li><a class="link" href="http://github.com/gsi-upm/maia" target="_blank" rel="noopener" +>Maia</a>: the Modular Architecture for Intelligent Agents is an evented agent architecture that aims to update the classical frameworks for intelligent agents with the concepts emerged from the Live Web.</li> +<li><a class="link" href="http://github.com/eestec/eestec.portal" target="_blank" rel="noopener" +>EESTEC.net</a>: the Plone based official portal of EESTEC. I fixed some bugs and implemented basic features.</li> +</ul> +<p>For more information, check my list of public repositories in <a href="http://github.com/balkian"><i class="fab fa-github"> Github</i></a>.</p>Searchhttps://balkian.com/search/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/search/ \ No newline at end of file diff --git a/page/page/1/index.html b/page/page/1/index.html new file mode 100644 index 0000000..4b5de75 --- /dev/null +++ b/page/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/page/ + \ No newline at end of file diff --git a/post/index.html b/post/index.html new file mode 100644 index 0000000..1daefae --- /dev/null +++ b/post/index.html @@ -0,0 +1,36 @@ +Posts +

Section

20 pages

Posts

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/post/index.xml b/post/index.xml new file mode 100644 index 0000000..cdabf7f --- /dev/null +++ b/post/index.xml @@ -0,0 +1,1825 @@ +Posts on J. Fernando Sánchezhttps://balkian.com/post/Recent content in Posts on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 07 Mar 2025 10:24:52 +0100RDF Is Deadhttps://balkian.com/p/rdf-is-dead/Fri, 07 Mar 2025 10:24:52 +0100https://balkian.com/p/rdf-is-dead/<p>A big part of my research has been around vocabularies and semantic annotation. +And, to be honest, I&rsquo;ve grown increasingly dissatisfied with the field. +To the point where I dread having to work on it. +Some day I will write about it in length, but today I&rsquo;ve stumbled upon a post that covers the topic quite well: <a class="link" href="https://terminusdb.com/blog/the-semantic-web-is-dead/" target="_blank" rel="noopener" +>The Semantic Web is Dead - <del>Long Live the Semantic Web</del></a> (styling mine).</p> +<p>In particular, this section has really resonated with me:</p> +<blockquote class="note"><h1 id="academics-and-industry">Academics and Industry +</h1><p>The political economy of academia and its interaction with industry is the origin of our current lack of a functional Semantic Web.</p> +<p>Academia is structured in a way that there is very little incentive for anyone to build usable software. Instead, you are elevated for rapidly throwing together an idea, a tiny proof of concept, and to iterate on microscopic variations of this thing to produce as many papers as possible.</p> +<p>In engineering, the devil is in the detail. You really need to get into the weeds before you can know what the right thing to do is. This is simultaneously a devastating situation for industry and academia. Nobody is going to wait around for a team of engineers to finish building a system to write about it in Academia. You’ll be passed immediately by legions of paper pushers. And in industry, you can’t just be mucking about with a system that you might have to throw away.</p></blockquote>Tips for efficient collaborationhttps://balkian.com/p/efficient-collaboration/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/p/efficient-collaboration/<h2 id="background">Background +</h2><blockquote class="note"><p>TL;DR I work in academia. This post focuses on advice I&rsquo;d give a younger me to be a more effective supervisor and project lead.</p></blockquote> +<p>My role in my research group has evolved from individual contributor to project lead that manages a team of multiple students. +This often involves coordinatating with other senior researchers and their teams.</p> +<p>This post is a collection of advice I would have given myself back when I started this journey. +It is also an excuse to reflect on these ideas I&rsquo;ve been implicitly applying everyday, and maybe learn a few things more in the process.</p> +<p>In my field, projects are often tied to a specific grant or some sort of public funding. +This means that the main concern of the lead is to ensure that the results at the end of the project match the description in the project proposal. +It also typically means maximizing the number of publications related to the project and their overall impact.</p> +<p>To do so, most projects rely on three types of staff: a) senior researchers (post-doc); b) junior researchers (PhD students); and c) interns doing their bachelor&rsquo;s or master&rsquo;s thesis. +The level of contribution is generally inversely propotional to the level of mastery of the contributor: PhD students design and develop the main contributions (both software and experimental) under the supervision of senior researchers (advisor/supervisor), and interns take care of tasks that are narrow in scope and not crucial to any academic contribution. +For instance, a PhD student may develop a new model for text classification, and an intern will wrap that model in an HTTP service with a nice UI. +When the service and UI part is intricate and has some potential academic merit, that task may be conducted by a PhD student as part of their thesis. +That was precisely the case with Senpy, which was part of my PhD thesis, and it has since been used by dozens of students to develop services in the context of other research projects.</p> +<h2 id="reasons-to-form-a-team">Reasons to form a team +</h2><p>In my opinion, a team has two advantages over a single contributor. +The first one is that collaboration often generates <strong>synergies</strong>, leading to surprising and enriching results (a team is greater than the sum of its parts) +Carefully selecting your team members and creating an environment that is conducing to these synergies is a topic on its own, and I will not get too deep into it here.</p> +<p>The second advantage can be summarized as <strong>concurrency</strong>: tasks can be tackled by more than one member. +This often implies some sort of parallelism. +Tasks tend to be split between different members, in hopes of speeding up the process. +But it can also be beneficial to have concurrency even in the absence of parallelism: different members can take turns solving the same problem. +This is common when the task requires exclusive access to a resource (e.g., performing an experiment on an expensive machine).</p> +<h2 id="challenges-of-teams-of-students">Challenges of teams (of students) +</h2><p>Just like in computer science, coordination in a concurrent project involves a non-negligible overhead. +The objective is to minimize this overhead.</p> +<p>I&rsquo;ve really struggled with managing teams in the past. +Given my context, I often attributed the failures to lack of time (having to juggle teaching and research), lack of training and preparation on the intern&rsquo;s side (they&rsquo;re often undergrads), lack of appeal or definition of the task (too academic-y), or any other external factors.</p> +<p>While all those aspects play an important role, some of them are out of our control as a project lead in academia: grants require a certain type of project and workflow, and the quality of our interns is bounded by the quality of our degrees. +So I think it is more constructive to focus on things that we can control. +In other words: we have to play the hand we&rsquo;re given.</p> +<p>Besides, there is no merit in achieving good results with excellent students/engineers. +They would succeed on their own even if you weren&rsquo;t there. +The real test for a good leader is succeeding with a subpar team.</p> +<p>In that vein, I&rsquo;ve reflected on my mistakes as a leader, and the inefficiencies of the teams around me. +I&rsquo;d classify my failures in the following areas:</p> +<ul> +<li>Delegation (and lack thereof). Piling up too many tasks and blocking progress.</li> +<li>Communication. Not having a coherent view of the state of affairs, the details of specific tasks, the general processes to follow, or the priorities of different tasks within a project.</li> +<li>Direction (or purpose). Not having a common direction</li> +</ul> +<h3 id="delegation">Delegation +</h3><p>I have a tendency to become a bottleneck in any project I am involved in: many tasks end up depending on me, either directly or indirectly. In the concurrency metaphor, I become a lock for many tasks, and a single executor for the rest. +This issue was not that apparent early on, when most of my work was as an individual contributor or I had the bandwidth to supervise and complete my tasks.</p> +<p>I think it is quite common to feel like delegating a task in these scenarios means:</p> +<ol> +<li>Defining the task in advance</li> +<li>Choosing an assignee for the task</li> +<li>Setting a deadline for the task</li> +<li>Explaining the task and the relevant context</li> +<li>Replying to multiple questions by e-mail or in person. Extra points when the questions make you wonder if any part of the explanation was ever clear.</li> +<li>Reviewing the results after the deadline</li> +<li>Realizing the assignee misunderstood the task or delivered something not even close to what you agreed upon</li> +<li>Going back to point 3.</li> +<li>When you&rsquo;re unlucky or short on time: giving up and doing the task yourself</li> +</ol> +<p>Many times, if felt like delegating tasks only lead to frustration and wasted time. +Especially when compared to the alternative:</p> +<ul> +<li>Defining the task</li> +<li>Setting a deadline</li> +<li>Finishing the task</li> +<li>Profit</li> +</ul> +<p>Luckily, some students and projects were an exception to this. +They worked autonomously and delivered something beyond the minimum requirements. +This reinforced my helplessness and the feeling that the problem was not being able to work with experienced engineers.</p> +<p>However, I now believe that the truth lies somewhere in between. +Sometimes your circumstances make it quite hard or inefficient to delegate tasks. +And some times may not be good candidates for delegation. +But most of the times you can take advantage of having an extra pair of hands, you just have to do that effectively.</p> +<h3 id="communication">Communication +</h3><p>Small teams rely on implicit knowledge more than they realize. +Even more so if the team is made up of highly specialized people that have worked in the same environment with mostly the same people for years.</p> +<p>Communication is a broad term. +It includes technical and concrete things such as how a certain task should be done. +But it also includes broader things like etiquette, organizational values, and who is more willing to help you out on certain topics on a Friday afternoon.</p> +<p>Here, I would take a page out of Python&rsquo;s zen and recommend that &ldquo;explicit is better than implicit&rdquo;. +Implicit (or tacit) knowledge comes with a whole set of drawbacks:</p> +<ul> +<li>It makes onboarding new users harder. Without a common knowledge base, all knowledge transference has to rely on personal interactions. Even worse, those interactions are probably organized on the spot, and are likely to miss important points.</li> +<li>It makes you heavily reliant on your current members (and their memory).</li> +<li>It impedes proper evaluationn and progress, since they are not written anywhere.</li> +<li>It increases the likelihood of misunderstandings when two members have conflicting beliefs, and makes it harder to detect them until it is too late.</li> +<li>It makes contradictions and (unknowingly) changing your mind much more likely. It can happen to the best of us, especially if you are involved in too many projects. When contraditions happen often, your colleagues will learn not to rely on your opinion.</li> +</ul> +<p>On the other hand, communication has to go both ways. +This means that your newer members need to be able to communicate when something is going wrong or can be improved (backpressure). +They should also feel free to talk about their motivation, state of mind, and feelings, <strong>when appropriate</strong>. +That last part is quite subjective, of course. +Try to find your - and your organization&rsquo;s - middle ground between &ldquo;I don&rsquo;t care how you feel, just do your job&rdquo; and &ldquo;sure, you can go to the Maldives on short notice. Oh, and don&rsquo;t worry about not having met a deadline in months, I&rsquo;m sure you&rsquo;re stressed and can use some vacation but will work remotely if we need you&rdquo;.</p> +<p>I personally feel a sweet spot is treating your coworkers like people, being empathetic and compassionate. +Part of being a good coworker is fulfilling the duties and obligations you accepted when signing your contract. +First and foremost, because not fulfilling them means someone else will have to work harder to make up for it. +And, secondly, because doing our part is the only way to move the organization (and research) forward.</p> +<h3 id="direction">Direction +</h3><p>By failure in direction I mean not keeping a consistent and shared set of general goals, principles and values in your organization. +In order to really take part in any enterprise, you need to have a clear understanding of the objectives and motivation. +When it comes to specific tasks, <em>what you&rsquo;re doing</em> is often not as important as the <em>why you&rsquo;re doing it</em>. +In fact, there may be times where you aren&rsquo;t truly sure of what exactly it is that you are doing, but you trust the process and the motivation behind the task.</p> +<p>I&rsquo;ve seen two failure modes in this regard. +The first one is to not have a clear direction. +The end result is that members of the team are not really that committed. +If no other <em>why</em> is provided, we are only left with <em>because they pay me to do it</em>. +And academia is not known to pay particularly well, to be honest.</p> +<p>The other mode is to provide contradicting or incompatible directions. +This can be in a short period of time, leading to the impression that there isn&rsquo;t really any conviction in the message. +But it can also be done over a longer period of time. +That can be perfectly acceptable, provided that the change in direction is justified and compatible with the principles of the organization.</p> +<p>Failure in direction is somewhat related to communication, but it is subtly different. +An organization can excel at communication, but change their direction constantly. +Arguably, a thorough communication strategy makes radical changes in direction less likely. +On the one hand, a change in direction needs to be documented, which can be a pain. +On the other hand, a written change is easier to spot and more likely to generate complaints.</p> +<h2 id="rules">Rules +</h2><p>I&rsquo;d argue that the path to successfully managing a research team lies in roughly the following key goals:</p> +<ul> +<li>Fostering autonomy</li> +<li>Avoiding miscommunication</li> +<li>Optimizing your contribution</li> +</ul> +<p>The remaining of the post will be a series of tasks or rules to achieve these goals.</p> +<blockquote class="warning"><p>Most of these ideas probably generalize well to collaboration outside of academia, but I hesitate to make more general claims.</p></blockquote> +<h3 id="fostering-automony">Fostering automony +</h3><p>The tips here are aimed at avoiding supervision overhead and training future leads.</p> +<h4 id="provide-a-simplified-version-of-the-bigger-picture">Provide a (simplified version of the) bigger picture +</h4><p>Try to paint the bigger picture, even for menial tasks within large projects. +For you, this may be the nth project you&rsquo;re involved in this year, but the new intern may not have even heard about European projects before. +Going back to the idea of direction, it is easier to work on something if you know the context of your work.</p> +<p>Having a general idea of the project and the context of your task will also help you make decisions on your own. +For instance, if I am told to develop a shiny new API for text classification, I may have to ask many questions: 1) what will be input look like?; 2) what should the parameters be?; 3) am I using POST or GET requests?; 4) should I return a JSON object or an XML?&hellip; +What if, instead of that, I am also told this API will be used in the context of project X, that our organization will be the only consumers of the API, and they also give me a link to the project&rsquo;s docs. +I may be able to figure out some of those answers on my own (e.g., by finding examples in the project&rsquo;s website), or decide that some questions are not vital at this point (e.g., if we are the only consumers, we can change from GET to POST if we need to much more easily).</p> +<p>One caveat here is that a link to the documentation or some vague words about the project do not constitute proper context. +You are responsible for summarizing the important bits of the context, providing instructions on how to navigate the reference materials, and being open to answer questions that may arise in the exchange.</p> +<h4 id="do-not-discuss-implementation-details-unless-strictly-necessary">Do not discuss implementation details unless strictly necessary +</h4><p>There is a fine line between discussing a non-trivial implementation detail and bikeshedding for hours about class names and code best practices. +For that reason, you should try to prioritize discussing high-level parts of the problem and the assignment, and trust the student to figure out the details on their own, or come back to you for clarification.</p> +<p>It is very common that students focus on very specific details when they are sharing their progress with their supervisors. +They will generally try to start by showing snippets of code and their results. +I find it helpful to remind them to explain their problems top-to-bottom, starting with a sentence or two about the context of their project, the description and motivation of the specific task they were doing, and the relationship with previous (and future) tasks. +That usually helps figure out the level of understanding of the student, whether there are any conceptual errors, and whether the specific block or problem is really worth discussing during the meeting.</p> +<p>Some technical problems will warrant a discussion in detail, either due to their complexity or their importance to the project. +In those cases, always limit the time you will spend on that specific issue ahead of time, and make sure to allow for some time at the end of the meeting to go back to any important high-level details.</p> +<p>If there are other students that worked on similar projects, do not hesitate to refer your new student to them. +It can be an opportunity for them to collaborate, and for the original student to work on explaining and teaching technical issues.</p> +<h4 id="provide-feedback">Provide feedback +</h4><p>Make a point of evaluating the results of each student on every level, and provide constructive and actionable feedback to them. +Even if no technical issues arise during the project, try to review the code and give some tips (e.g., formatting, code structure, DRY). +Try to focus on bigger issues and enforcing best practices before nitpicking and giving feedback on small subjective improvements.</p> +<p>Make it clear when your feedback is objective/best practice (e.g., a function is deprecated) and when it is a matter of preference. +If it is the latter, try to provide more than one alternative, to encourage them to think about it and make an educated decision.</p> +<h4 id="take-documentation-and-knowledge-transfer-seriously">Take documentation and knowledge transfer seriously +</h4><p>Taking the time to write down basic documentation can save a lot of time in the long run. +Besides, most of the job of mentoring a new student is lost when that student finishes their degree and leaves to find a job in industry. +Good documentation can remain in your organization and be extended long after the intern is gone.</p> +<p>This is very obvious for specific tools, whether internal or public. +Good documentation means any new member can check the tool and use it without much assistance. +Even better documentation helps newcomers contribute to the tool. +Make sure to make it clear who to approach if something is missing from the documentation, and make it easier to do so than to make assumptions and use the tool incorrectly.</p> +<p>Writing documentation can be very time consuming, and sometimes it is hard to know exactly what things to focus on when writing the docs. +You need to anticipate the needs of the future user. +If you are short on time, a good strategy is to delegate the writing of the documentation. +Instead of going into details, you can write a very barebones version and training a new user to use and contribute to your tool. +Then, leave it up to the new user to extend the documentation, including more details and pitfalls. +As a bonus, reading and fixing the docs will give you a better sense of how well that new user understands the tool, as well as possible improvements.</p> +<p>This tip also applies to more general areas such as machine learning, graph neural networks, or simulation. +Just remember you do not need to reinvent the wheel in those cases. +A simple summary and a list of references to expand on the topic could be more than enough. +Make sure to also include any specifics that apply to your organization. +For instance, point to repositories on github (public or private) that can be used to explore the topic, examples of similar projects in the domain, etc.</p> +<p>Identify what information is important for any new hire and present it to them as clearly as possible. +Part of that information should be where and how common knowledge is stored and shared, should they need more information in the future. +Make this documentation as easy to discover and consume as possible. +Centralizing this common information in the form of a wiki is often a good idea.</p> +<p>Lastly, make it easy for any member of your organization to update this common documentation, and encourage them to do so. +Whenever a member asks you something useful that is not documented, don&rsquo;t just answer the question. +Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer. +If your organization&rsquo;s culture does not encourage using these docs, they will quickly get outdated and fall out of use.</p> +<p>One example of taking this documentation approach really seriously is <a class="link" href="https://oxide.computer/" target="_blank" rel="noopener" +>Oxide (computer company)</a>. +They have a process they call <a class="link" href="https://rfd.shared.oxide.computer/" target="_blank" rel="noopener" +>Request For Discussion (RFD)</a>, which they use to discuss and document both technical and organizational decisions. +For instance, they have <a class="link" href="https://rfd.shared.oxide.computer/rfd/0537" target="_blank" rel="noopener" +>RFDs on why they record every meeting</a>, <a class="link" href="https://rfd.shared.oxide.computer/rfd/0110" target="_blank" rel="noopener" +>RFDs about their choice of database</a>, and even <a class="link" href="https://rfd.shared.oxide.computer/rfd/0001" target="_blank" rel="noopener" +>an meta-RFD that discusses the motivation RFDs and how the process should work</a>.</p> +<h4 id="trust-your-teammates-ability-to-learn">Trust your teammate&rsquo;s ability to learn +</h4><p>I&rsquo;ve been bitten by this way too many times. +Your students are probably more capable of learning than you think, especially if you have set up your documentation right. +What they lack in experience, they make up for with free time, a (more) neuroplasticity and determination.</p> +<p>Sure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.</p> +<h4 id="use-tools-wisely">Use tools wisely +</h4><p>Your students probably have little experience with code versioning, reviewing processes, time management, etc. +A good choice of tools and some training can go a long way and make your life much easier in the long run. +It will also give your students a taste of what working in a bigger/real company feels like and a head start.</p> +<p>For instance, using git makes it easier to collaborate on code. +It also ensures that your results will not be lost if your student&rsquo;s laptop gets stolen.</p> +<p>Using GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students. +It will force them to commit working code, and it will make it easier to check their results and discuss the end result.</p> +<p>Using overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results. +You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration. +In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.</p> +<p>Also, on a related note, make sure every team member has a proper development setup. +It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work. +It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.</p> +<h4 id="encourage-cooperation">Encourage cooperation +</h4><p>Do not become the center of every conversation. +If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.</p> +<p>The ability to discuss with your peers and report only when needed will be extremely important for them in the future. +They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are). +That might lead to valuable insights and improvements for your team and project.</p> +<p>Moreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.</p> +<h4 id="reward-proactivity">Reward proactivity +</h4><p>The whole point of this section is to get your team to work independently when possible. +Be explicit about this goal to make sure it is clear to everyone. +And encourage behavior that aligns with this goal, even on a small scale.</p> +<p>For instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements. +Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time. +Do not jump straight to criticize it. +Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.</p> +<h4 id="dont-be-a-perfectionist">Don&rsquo;t be a perfectionist +</h4><p>Perfect is the enemy of done. +It is also the enemy of a happy co-worker.</p> +<p>Try to remember that you are dealing with students, and you were probably no better at their age. +Besides, you probably delegated the task beause you did not have any spare time to do it yourself. +<code>FIXME</code> is often better than <code>TODO</code>.</p> +<p>Take the opportunity to provide some feedback and teach them something useful. +Some mistakes are also worth adding to your documentation, or presenting to other students in a presentation.</p> +<h3 id="avoiding-miscommunication">Avoiding miscommunication +</h3><p>A common source of wasted effort and unnecessary back-and-forth is miscommunication. +These are some tips to help keep everyone on the team informed and aligned.</p> +<h4 id="make-priorities-clear">Make priorities clear +</h4><p>All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks. +This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.</p> +<h4 id="define-boundaries-and-abstractions">Define boundaries (and abstractions) +</h4><p>Once again, the goal is generally to achieve some sort of parallelism between your team members. +In order to do that, they need to know how they will interact with each other.</p> +<p>On a more general level, this means knowing the responsibilities and scope of your work.</p> +<p>On a more specific level, it means knowing their dependency graph. +In other words, whether the progress of one team member will depend on the results of another one. +Whenever there is a dependency, the interface should be made very clear. +This often takes the form of an API, a file with a given format, or a section of a document.</p> +<p>Take some time to define the boundary as precisely as needed at that point in the project. +I would suggest having specific examples that you can discuss and modify. +It is hard to discuss in the abstract, especially for inexperienced contributors. +When in doubt, default to the simplest option (e.g., a common file vs using a database). +Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there. +Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.</p> +<p>One type of failure I&rsquo;ve seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples. +That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.</p> +<h4 id="be-approachable">Be approachable +</h4><p>Did I wrote a whole section about autonomy? Yes. +Is the end goal to do more and talk less? Also yes. +Thing is, no process is perfect, and misunderstandings are bound to happen at some point. +If your only response to questions is a grumpy face or a &ldquo;read the freaking docs&rdquo;, your students will not alert you when something really needs your attention, and you will find out too late. +For instance, the documentation may be unclear, or your processes may be inadvertedly alienating new members and making new hires harder.</p> +<p>Another way to be approachable is to be clear about your shortcomings, and whether something you are saying is negotiable and/or debatable. +My rule of thumb is to err on the side of negotiable, and only be strict when it is really necessary (e.g., time constraints or an unproductive student). +We are all more likely to finish our tasks if we feel them ours, if we a say in how and when to perform them.</p> +<p>Just to be clear, approachable does not mean you have to be their confident or their best friend. +It also does not mean that it is okay to challenge or question you continuously. +Some times it is okay to simply say &ldquo;just do as I say&rdquo;.</p> +<h4 id="review-frequently">Review frequently +</h4><p>One type of review is individual. +It involves reviewing code on github, or reading deliverables and papers on overleaf. +It can help catch misunderstandings, and measure the true rate of progress in the individual tasks. +The other type of review is done as a group, by going through the key progress and action points. +This type of review helps everyone stay on the same page, and catch any general drifts in the project, such as misaligned priorities.</p> +<p>The frequency of each type of review depends on the specific nature of the project, the types of tasks being performed by the student, and your confidence on the student&rsquo;s abilities.</p> +<h3 id="optimizing-your-contribution">Optimizing your contribution +</h3><p>Tips on optimizing your contribution to the team.</p> +<h4 id="prioritize-prioritize-prioritize">Prioritize, prioritize, prioritize +</h4><p>Part of your job as a project lead is to identify the main goals in a project and to prioritize the tasks that will lead you there. +On the other hand, you are part of a research group, and you should be actively involved in its health and future. +Lastly, you are also in charge of the life-long project that is your research career.</p> +<p>In all these cases, your goal should be to identify the long term goals, come up with a sound strategy, and prioritize the tasks that will lead you and your group there. +Keeping your priorities straight will help you make steady progress, and avoid bikeshedding and changing goalposts. +It will also help you steer your progress in the right direction, since we all have limited time and effort and can&rsquo;t do everything at once.</p> +<p>The fact that your time is limited also means that you will need to decide how to prioritize these three roles. +I&rsquo;ve listed them in increasing level of importance for me. +It means that it is okay to focus on a specific project for a while, but if progress in your career is stalled - usually through publication - you need to reevaluate and concentrate your efforts on that.</p> +<h4 id="be-okay-with-short-term-inefficiencies">Be okay with (short-term) inefficiencies +</h4><p>I&rsquo;ve personally struggled with delegating tasks that will take me orders of magnitude less work than they will a student. +Thing is, most tasks will fall under this category, and your time is limited, so you have to delegate if you want to have time for more important matters. +If you never delegate any tasks, you are not allowing your team to learn and catch up on whatever technical skills are required. +Besides, you are not improving yourself on the managerial side of things. +It turns out delegating is hard, it requires a whole set of non-technical skills. +I suspect this is oftentimes the reason we don&rsquo;t do delegate in the first place: delegating is hard, and technical tasks are usually more straightforward, so we just don&rsquo;t want to do the work.</p> +<h4 id="dont-neglect-training">Don&rsquo;t neglect training +</h4><p>You are a senior researcher. +You probably know how to solve problems in your domain quite efficiently. +In my case, that means processing data and developing code.</p> +<p>That means I could dedicate my days to processing data and developing new code for my group. +That group would likely be used in multiple projects. +However, there is a hard limit to how much code I can push out in a day, especially if you take into account other obligations such as teaching.</p> +<p>A wiser strategy would be to set aside some of that coding time to instead help students become better programmers. +Firstly, because those students will be thankful and more motivated to work than when they are left to learn on their own without much guidance. +Secondly, because those students will then be more prepared to help me out if I delegate a task to them. +And, lastly, because these students have a whole life in fron of them. +A life full of big projects of their own, and contributions to society. +That little training time can have a compounding effect in the future.</p> +<h4 id="set-a-time-limit-for-your-interactions-in-advance">Set a time limit for your interactions in advance +</h4><p>Really long slots can easily lead to bikeshedding and going unnecessarily deep into implementation details, which is clearly an inefficient use of your time. +Even worse, our attention span and memory are finite, so longer and dense meetings can lead to fatigue and to missing or dilluting important points in the conversation.</p> +<p>For these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary. +You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.</p> +<h2 id="beyond-your-team">Beyond your team +</h2><p>The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control. +But teams rarely work in isolation, you will most likely +In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization&rsquo;s culture and sense of belonging.</p> +<p>Many of the aspects I talked about in the team section apply here. +For instance, the obsession with documentation can - and should - be applied organization-wise. +The same goes for defining boundaries and using concrete examples when collaborating with other teams. +For most intents and purposes, you can treat other teams as another contributor to your team. +Just one that will be more costly and slow to interact with.</p> +<p>If possible, I&rsquo;d try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved. +Avoid involving whole teams in discussions when the broad strokes have not been defined yet. +The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.</p> +<p>On the organization&rsquo;s side, I would suggest having an honest conversation about your core principles. +I really liked <a class="link" href="https://www.youtube.com/watch?v=9QMGAtxUlAchttps://www.youtube.com/watch?v=9QMGAtxUlAc" target="_blank" rel="noopener" +>Bryan Cantrill&rsquo;s talk about principles of technology leadership</a>. +He goes deep into the effects that principles have had on well known companies, and how to go about defining your company&rsquo;s principles. +I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.</p> +<p>More generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive. +And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier. +Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.</p>Bridging RDF, JSON-LD and Dataclasseshttps://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/<p>In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.</p> +<h2 id="the-rdflib-way">The <code>rdflib</code> way +</h2><p><code>rdflib</code> only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, <code>Entity</code>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">entry</span> <span class="o">=</span> <span class="n">Entry</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">entry</span><span class="p">[</span><span class="s1">&#39;vocab:property&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">jsonld</span><span class="p">())</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Would print something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@id&#34;</span><span class="p">:</span> <span class="s2">&#34;:Entry_202505....&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;prefix:Entity&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;vocab:property&#34;</span><span class="p">:</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the</p> +<p>Pros:</p> +<ul> +<li>Flexible/extensible</li> +<li>Lightweight. This is mostly JSON-LD in Python&rsquo;s clothing.</li> +<li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li> +</ul> +<p>Cons:</p> +<ul> +<li>Discoverability. Documentation and examples are needed to know which attributes to use</li> +<li>Error-prone. It is easy to misuse a property, or introduce typos</li> +<li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li> +</ul> +<h2 id="the-object-oriented-way">The object-oriented way +</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like <a class="link" href="https://pydantic.dev/" target="_blank" rel="noopener" +>pydantic</a> that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p> +<p>This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p> +<p>Pros:</p> +<ul> +<li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li> +<li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li> +</ul> +<p>Cons:</p> +<ul> +<li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li> +<li>Polymorphism.</li> +</ul> +<h2 id="a-hybrid-approach">A hybrid approach +</h2><p>Whichever solution is chosen in the end, it needs to:</p> +<ul> +<li>Make it easy and error-proof to add the most common types of annotations</li> +<li>Allow for additional annotations/attributes to be added</li> +<li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li> +<li>Allow for deserialization of custom types</li> +<li>Allow multiple consumers to add their own annotations</li> +</ul>uv - One rust tool to rule all pythonshttps://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/Mon, 17 Feb 2025 23:02:47 +0100https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/<img src="https://balkian.com/img/uv.png" alt="Featured image of post uv - One rust tool to rule all pythons" /><p>Long story short: I&rsquo;m now using <a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a>, and so should you. +It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.</p> +<h2 id="context">Context +</h2><p>For years, my strategy to manage python projects has been a mix of a custom <code>setup.py</code>, several hand-crafted <code>requirements.txt</code> files (through <code>pip freeze</code>), a custom virtualenv per project, and multiple tools to upload to PyPI. +Although this works, this setup has many drawbacks:</p> +<ul> +<li>It requires user intervention (creating a venv, sourcing it, handling new deps). This isn&rsquo;t ideal if you want new (probably inexperienced) users to use your projects.</li> +<li>On a similar note, the whole process needs to be well documented if you want other users to contribute or maintain the code.</li> +<li>Pinning dependency versions is finicky, and I&rsquo;ve run into problems beause of that.</li> +<li>Creating a new project involves a template, or copying files from an older project.</li> +</ul> +<p>Of course, this is nothing new. +There is a whole site dedicated to <a class="link" href="https://packaging.python.org/en/latest/" target="_blank" rel="noopener" +>packaging your Python project</a>. +A plethora of different projects have come and go, with varying degrees of success.</p> +<h2 id="alternatives-poetry">Alternatives (poetry) +</h2><p>About a year before trying <code>uv</code>, I tried to catch up with the ecosystem and get to know the <code>blessed new way</code>. +However, the task proved to be a little more difficult, as the landscape is filled with a myriad of alternatives, each with their own set of drawbacks and detractors. +Packaging has historically been a weak spot, in ironical contradiction to the Zen of Python&rsquo;s &ldquo;There should be one&ndash; and preferably only one &ndash;obvious way to do it&rdquo;,</p> +<p>I eventually settled on <a class="link" href="https://python-poetry.org/" target="_blank" rel="noopener" +>poetry</a>. +Mostly because it seemed like the most popular alternative.</p> +<p>There are many things I liked about it. +First of all, having a convention for dependencies (<code>pyproject.toml</code>) and a tool that properly handles them was nice. +It also removed the need to remember specific incantations to build and publish my Python projects. +Lastly, I mixed it <code>poetry2nix</code> to create reproducible python environments using nix. +This makes for a very powerful experience.</p> +<p>However, there were multiple hiccups. +First of all, it took me some time to figure out which specific fields to use (each tool can define ad-hoc properties in a the <code>pyproject.toml</code> file), and some of them seemed redundant with the more generic ones. +Full disclosure, this specific point might be a mistake on my side, and I do not remember the details. +The second one is speed. +(Re-)creating an environment took a non-negligible amount of time.</p> +<h2 id="enter-light-uv">Enter <del>light</del> <code>uv</code> +</h2><p>According to its repository, <code>uv </code>can replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +Not only that, but it also claims to do that 10-100 times faster than pip. +I must admit that it being written in rust was a another selling point for me, as I&rsquo;m looking for excuses to collaborate in a decently-sized rust projejct.</p> +<p>Installing it is dead simple: simply download the binary (e.g., with curl) or run <code>pip install uv</code>. +You won&rsquo;t need much more: <code>uv</code> seems to just do the right thing out of the box. +And it does it really, really fast. +The rest of the time it gets out of the way.</p> +<p>My only gripe so far is that I don&rsquo;t seem to find a built-in command to drop into a shell, but that is nothing that <code>uv run $SHELL</code> cannot fix.</p> +<h2 id="common-operations">Common operations +</h2><h3 id="initialize-a-repository">Initialize a repository +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv init +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="adding-dependencies">Adding dependencies +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv add senpy +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="running-commands-inside-the-environment">Running commands inside the environment +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv run &lt;COMMAND&gt; +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"># e.g., run a shell using your python version and dependencies +</span></span><span class="line"><span class="cl">uv run $SHELL +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="dependency-tree">Dependency tree +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv shell +</span></span><span class="line"><span class="cl">Resolved 44 packages in 1ms +</span></span><span class="line"><span class="cl">my-project v0.1.0 +</span></span><span class="line"><span class="cl">├── fastapi[standard] v0.115.8 +</span></span><span class="line"><span class="cl">│ ├── pydantic v2.10.6 +</span></span><span class="line"><span class="cl">│ │ ├── annotated-types v0.7.0 +</span></span><span class="line"><span class="cl">│ │ ├── pydantic-core v2.27.2 +</span></span><span class="line"><span class="cl">│ │ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── starlette v0.45.3 +</span></span><span class="line"><span class="cl">│ │ └── anyio v4.8.0 +</span></span><span class="line"><span class="cl">│ │ ├── exceptiongroup v1.2.2 +</span></span><span class="line"><span class="cl">│ │ ├── idna v3.10 +</span></span><span class="line"><span class="cl">│ │ ├── sniffio v1.3.1 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── email-validator v2.2.0 (extra: standard) +</span></span><span class="line"><span class="cl">│ │ ├── dnspython v2.7.0 +</span></span><span class="line"><span class="cl">... +</span></span></code></pre></td></tr></table> +</div> +</div>Nix Recipe for Python Projectshttps://balkian.com/p/nix-recipe-for-python-projects/Mon, 13 Nov 2023 18:21:46 +0100https://balkian.com/p/nix-recipe-for-python-projects/<p>This is a quick and easy recipe to add a <code>default.nix</code> to any Python project with a <code>requirements.txt</code> file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="k">with</span> <span class="kn">import</span> <span class="sr">&lt;nixpkgs&gt;</span> <span class="p">{</span> <span class="p">};</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">let</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span> <span class="o">=</span> <span class="n">python311Packages</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="k">rec</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;impurePythonEnv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">venvDir</span> <span class="o">=</span> <span class="s2">&#34;./.venv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">buildInputs</span> <span class="o">=</span> <span class="p">[</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># A python interpreter including the &#39;venv&#39; module is required to bootstrap</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the environment.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">python</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># This execute some shell code to initialize a venv in $venvDir before</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># dropping into the shell</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">venvShellHook</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Those are dependencies that we would like to use from nixpkgs, which will</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># add them to PYTHONPATH and thus make them accessible from within the venv.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">numpy</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">requests</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># In this particular example, in order to compile any binary extensions they may</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># require, the python modules listed in the hypothetical requirements.txt need</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the following packages to be installed locally:</span> +</span></span><span class="line"><span class="cl"> <span class="n">taglib</span> +</span></span><span class="line"><span class="cl"> <span class="n">openssl</span> +</span></span><span class="line"><span class="cl"> <span class="n">git</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxml2</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxslt</span> +</span></span><span class="line"><span class="cl"> <span class="n">libzip</span> +</span></span><span class="line"><span class="cl"> <span class="n">zlib</span> +</span></span><span class="line"><span class="cl"> <span class="p">];</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Now we can execute any commands within the virtual environment.</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># This is optional and can be left out to run pip manually.</span> +</span></span><span class="line"><span class="cl"> <span class="n">postShellHook</span> <span class="o">=</span> <span class="s1">&#39;&#39; +</span></span></span><span class="line"><span class="cl"><span class="s1"> pip install -r requirements.txt +</span></span></span><span class="line"><span class="cl"><span class="s1"> &#39;&#39;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now, you will get a clean environment by running:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">nix-shell +</span></span></code></pre></td></tr></table> +</div> +</div>Kanata: advanced keyboard configurationhttps://balkian.com/p/kanata-advanced-keyboard-configuration/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/p/kanata-advanced-keyboard-configuration/<p><a class="link" href="https://github.com/jtroo/kanata/" target="_blank" rel="noopener" +>Kanata</a> is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you&rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.</p> +<p>The project was inspired by the more popular <a class="link" href="https://github.com/kmonad/kmonad" target="_blank" rel="noopener" +>KMonad</a>, and the author cites some of the <a class="link" href="https://github.com/jtroo/kanata/blob/main/docs/kmonad_comparison.md" target="_blank" rel="noopener" +>differences</a>. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +<a class="link" href="https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd" target="_blank" rel="noopener" +>Here&rsquo;s a very complete config that serves as documentation</a>.</p> +<p>One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.</p> +<p><a class="link" href="https://github.com/rvaiya/keyd/" target="_blank" rel="noopener" +>Keyd</a> is another alternative with a more declarative configuration format, which might lend itself to smaller.</p> +<p>For now I&rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren&rsquo;t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that&rsquo;s the idea. +We&rsquo;ll see if I like it enough to stick with it.</p> +<p>For now, here&rsquo;s my very simple config:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span><span class="lnt">41 +</span><span class="lnt">42 +</span><span class="lnt">43 +</span><span class="lnt">44 +</span><span class="lnt">45 +</span><span class="lnt">46 +</span><span class="lnt">47 +</span><span class="lnt">48 +</span><span class="lnt">49 +</span><span class="lnt">50 +</span><span class="lnt">51 +</span><span class="lnt">52 +</span><span class="lnt">53 +</span><span class="lnt">54 +</span><span class="lnt">55 +</span><span class="lnt">56 +</span><span class="lnt">57 +</span><span class="lnt">58 +</span><span class="lnt">59 +</span><span class="lnt">60 +</span><span class="lnt">61 +</span><span class="lnt">62 +</span><span class="lnt">63 +</span><span class="lnt">64 +</span><span class="lnt">65 +</span><span class="lnt">66 +</span><span class="lnt">67 +</span><span class="lnt">68 +</span><span class="lnt">69 +</span><span class="lnt">70 +</span><span class="lnt">71 +</span><span class="lnt">72 +</span><span class="lnt">73 +</span><span class="lnt">74 +</span><span class="lnt">75 +</span><span class="lnt">76 +</span><span class="lnt">77 +</span><span class="lnt">78 +</span><span class="lnt">79 +</span><span class="lnt">80 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defcfg</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Your keyboard device will likely differ from this.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">linux-dev</span> <span class="nv">/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Windows doesn&#39;t need any input/output configuration entries; however, there</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; must still be a defcfg entry. You can keep the linux-dev entry or delete</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; it and leave it empty.</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsrc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="nf">-</span> <span class="nf">=</span> <span class="nv">bspc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">e</span> <span class="nv">r</span> <span class="no">t</span> <span class="nv">y</span> <span class="nv">u</span> <span class="nv">i</span> <span class="nv">o</span> <span class="nv">p</span> <span class="nv">[</span> <span class="nv">]</span> +</span></span><span class="line"><span class="cl"> <span class="nv">caps</span> <span class="nv">a</span> <span class="nv">s</span> <span class="nv">d</span> <span class="nv">f</span> <span class="nv">g</span> <span class="nv">h</span> <span class="nv">j</span> <span class="nv">k</span> <span class="nv">l</span> <span class="c1">; &#39; ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">\ z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">v</span> <span class="nv">b</span> <span class="nv">n</span> <span class="nv">m</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctl</span> <span class="nv">lmet</span> <span class="nv">lalt</span> <span class="nv">spc</span> <span class="nv">ralt</span> <span class="nv">rmet</span> <span class="nv">rctl</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">qwerty</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@warrows</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@slsft</span> <span class="nv">@dlalt</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@jrmet</span> <span class="nv">@kralt</span> <span class="nv">@lrsft</span> <span class="nv">@</span><span class="c1">;rctrl _ _</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">arrows</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">left</span> <span class="nv">down</span> <span class="nv">up</span> <span class="nv">rght</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">colemak</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">f</span> <span class="nv">p</span> <span class="nv">b</span> <span class="nv">j</span> <span class="nv">l</span> <span class="nv">u</span> <span class="nv">y</span> <span class="c1">; [ ] </span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@rlsft</span> <span class="nv">@slalt</span> <span class="nv">@tlmet</span> <span class="nv">g</span> <span class="nv">m</span> <span class="nv">@nrmet</span> <span class="nv">@eralt</span> <span class="nv">@irsft</span> <span class="nv">@orctrl</span> <span class="o">&#39;</span> <span class="nv">ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">XX</span> <span class="nv">z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">d</span> <span class="nv">v</span> <span class="nv">k</span> <span class="nv">h</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">@smartspace</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">magic</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">@clmk</span> <span class="nv">@qwerty</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">tab</span> <span class="nv">A-tab</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">bspc</span> <span class="nv">esc</span> <span class="nv">_</span> <span class="nv">ret</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defalias</span> +</span></span><span class="line"><span class="cl"> <span class="nv">warrows</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">w</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">arrows</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">alctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">a</span> <span class="nv">lctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">dlalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">d</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">flmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">f</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">jrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">j</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">k</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lrsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">l</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;rctrl (tap-hold 200 200 ; rctrl)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">rlsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">r</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tlmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="no">t</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">nrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">n</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">eralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">e</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">irsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">i</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">orctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">o</span> <span class="nv">rctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">clmk</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">colemak</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">qwerty</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">qwerty</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">smartspace</span> <span class="p">(</span><span class="nv">tap-dance</span> <span class="mi">200</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="nv">spc</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">magic</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="p">(</span><span class="nv">one-shot</span> <span class="mi">300</span> <span class="nv">lalt</span><span class="p">)</span> <span class="nv">spc</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">a</span> +</span></span><span class="line"><span class="cl"> <span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MB850 combi in linuxhttps://balkian.com/p/logitech-mb850-combi-in-linux/Sat, 30 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mb850-combi-in-linux/<p>As a follow-up to my last post, I&rsquo;ve decided to also configure my mk850 combo (k850 + m720 triathlon).</p> +<p>Some notes:</p> +<ul> +<li>The keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc)</li> +<li>The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I&rsquo;ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM.</li> +</ul> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">evdev:input:* </span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_70039</span><span class="o">=</span><span class="s">leftctrl # bind capslock to w </span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0005v046DpB015*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e0</span><span class="o">=</span><span class="s">f19 +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e2=unknown +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7002b=unknown</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the settings have been applied by running <code>evemu-describe</code>:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> sudo /sbin/evemu-describe /dev/input/event&lt;id of your device&gt; <span class="p">|</span> grep KEY_ +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MX Keys for Mac on Linuxhttps://balkian.com/p/logitech-mx-keys-for-mac-on-linux/Fri, 29 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mx-keys-for-mac-on-linux/<p>I recently got Logitech MX Keys for Mac keyboard at work. +The German version, to be more precise. +This version was three times cheaper than the Windows equivalent with either US or ES layout. +Since I touch type anyway, I thought it was a bargain.</p> +<p>As soon as I plugged it in, I realized there were some glaring issues with the keyboard. +First of all, the Meta/Super and Alt keys are reversed in this keyboard. +In the normal/full version of this keyboard, Logitech gives an option to choose between Mac, Windows and iOS host, and that changes the behavior of the keys. +In this version, tho, only iOS and Mac are available.</p> +<p>Besides that, there&rsquo;s the issue of the grave (tilde) and angle keys switched as well.</p> +<p>Switching these keys around would be very easy with Xorg, but Wayland once again complicates things&hellip;</p> +<p>These issues almost made me return the keyboard. +Luckily, tho, there is another option: configuring the keys one level lower than wayland (and X11), through hwdb.</p> +<p>Long story short, this will configure any Logitech keyboard with the same product id (0x4092) to use a saner configuration:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="c1">#File: /etc/udev/hwdb.d/90-logitech-keyboard.hwdb</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0003v046Dp4092*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e2</span><span class="o">=</span><span class="s">leftmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e3=leftalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70039=leftctrl +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70064=102nd +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70035=grave +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e7=rightalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e6=rightmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7006d=compose</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div>Linux on the Microsoft Surface Gohttps://balkian.com/p/linux-on-the-microsoft-surface-go/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/p/linux-on-the-microsoft-surface-go/<p>Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. +These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.</p> +<h2 id="installing-the-kernel">Installing the kernel +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --depth <span class="m">1</span> https://github.com/jakeday/linux-surface.git ~/linux-surface +</span></span><span class="line"><span class="cl">cp -a ~/linux-surface /media/&lt;your usb&gt; +</span></span></code></pre></td></tr></table> +</div> +</div><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -a /media/&lt;your usb&gt;/linux-surface ~/ +</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/linux-surface/ +</span></span><span class="line"><span class="cl">sudo sh setup.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="booting-ubuntu-first">Booting ubuntu first +</h2><p>Switch out of Windows S mode.</p> +<p>Boot into the &ldquo;Command Prompt&rdquo;.</p> +<p>From Windows go to &ldquo;change advanced startup options&rdquo; and select &ldquo;restart now&rdquo;.</p> +<p>When it reboots, choose the &ldquo;Troubleshoot&rdquo; option, then choose the &ldquo;Advanced options&rdquo; option, and finally choose the &ldquo;Command Prompt&rdquo; option.</p> +<p>After the device reboots, login to the command prompt and then you should see a terminal with X:\windows\system32&gt;</p> +<p>At the prompt, check your UEFI entries:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /enum firmware +</span></span></code></pre></td></tr></table> +</div> +</div><p>Copy UEFI entry of &ldquo;Windows Boot Manager&rdquo; to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d &ldquo;Ubuntu&rdquo;</p> +<p>Copy the printed GUID number including the braces {} using Ctrl+C</p> +<p>Set file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \EFI\ubuntu\grubx64.efi</p> +<p>Set Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /set <span class="o">{</span>fwbootmgr<span class="o">}</span> displayorder <span class="o">{</span>guid<span class="o">}</span> /addfirst +</span></span></code></pre></td></tr></table> +</div> +</div><p>Check your UEFI entries again: bcdedit /enum firmware You should see something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">Firmware Boot Manager +</span></span><span class="line"><span class="cl">--------------------- +</span></span><span class="line"><span class="cl">identifier <span class="o">{</span>fwbootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl">displayorder <span class="o">{</span>3510232e-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>bootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>2148799b-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a67-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a68-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl">timeout <span class="m">0</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.</p>Controlling Zigbee devices with MQTThttps://balkian.com/p/controlling-zigbee-devices-with-mqtt/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/p/controlling-zigbee-devices-with-mqtt/<p>This is a short tutorial on connecting a zigbee device (an Aqara cube) +to an MQTT server, so you can control your zigbee devices from the +network.</p> +<p>If you&rsquo;re anything like me, you&rsquo;re probably a sucker for IoT devices. +For a long time, I&rsquo;ve been using WiFi-enabled lights, and Amazon dash +buttons to control them. To keep these (cheap Chinese) internet enabled +devices away from your network and their respective cloud services, +you&rsquo;ll probably want to set up a dedicated network in your router (more +on this on a future post, maybe). Another disadvantage of WiFi devices +is that they&rsquo;re relatively power hungry.</p> +<p>A popular alternative is using ZigBee for communication. It is a +dedicated protocol similar to bluetooth (BLE), with lower power +requirements and bitrate.</p> +<p>Take the (super cute) aqara cube as an example. It is a small cube that +detects rotation on all of its axes, and tapping events. Here&rsquo;s a +video:</p> +<div class="video-wrapper"> +<iframe loading="lazy" +src="https://www.youtube.com/embed/5YtqG1wEnng" +allowfullscreen +title="YouTube Video" +> +</iframe> +</div> +<p>To connect to zigbee devices you will need a zigbee enabled gateway +(a.k.a. hub), which connects to your WiFi network and your zigbee +devices. Once again, this means adding an internet-enabled device to +your home, and probably a couple of cloud services.</p> +<p>As an alternative, you can set up your own zigbee gateway, and control +it to your home automation platform of choice (e.g. home assistant). We +will cover how to set up a zigbee2mqtt gateway that is also connected to +an MQTT server, so you can use MQTT to control your devices and get +notifications.</p> +<p>What you need:</p> +<ul> +<li><a class="link" href="https://www.aliexpress.com/item/Original-Xiaomi-Mi-Aqara-Cube-Smart-Home-Controller-6-Action-Operation-Fr-Home-Device-Zigbee-Version/32892947622.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>Aqara +cube</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/Wireless-Zigbee-CC2531-CC2540-Zigbee-Sniffer-Bluetooth-BLE-4-0-Dongle-Capture-Module-USB-Programmer-Downloader/32907587711.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC2531 zigbee +sniffer</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/CFSUNBIRD-CC-DEBUGGER-Debugger-and-Programmer-for-RF-System-on-Chips-TI-ORIGINAL-Fast-hipping/32813122315.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC-debugger</a>.</li> +</ul> +<p>You will need to flash your sniffer. For that, you only need to follow +the instructions from the <a class="link" href="https://koenkk.github.io/zigbee2mqtt/" target="_blank" rel="noopener" +>zigbee2mqtt +documentation</a>.</p> +<p>Once you&rsquo;re done flashing, you&rsquo;re ready to set up the zigbee2mqtt +server. For convenience, I wrote a simple docker-compose to deploy a +zigbee2mqtt server and a test mosquitto server:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;2.1&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">zigbee2mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">koenkk/zigbee2mqtt</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">zigbee2mqtt </span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./z2m-data/:/app/data/</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">devices</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="s2">&#34;/dev/ttyACM0&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">eclipse-mosquitto</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">1883</span><span class="p">:</span><span class="m">1883</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">9001</span><span class="p">:</span><span class="m">9001</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./mosquitto.conf:/mosquitto/config/mosquitto.conf</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hass</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">overlay</span><span class="w"> +</span></span></span></code></pre></td></tr></table> +</div> +</div><p>You can test your installation with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">❯ mosquitto_sub -h localhost -p <span class="m">1883</span> -t <span class="s1">&#39;zigbee2mqtt/#&#39;</span> +</span></span><span class="line"><span class="cl">online +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:149,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;rotate_right&#34;</span>,<span class="s2">&#34;angle&#34;</span>:12.8<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;slide&#34;</span>,<span class="s2">&#34;side&#34;</span>:2<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:120<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;wakeup&#34;</span><span class="o">}</span></span></span></code></pre></td></tr></table> +</div> +</div> +<p>zigbee2mqtt supports the following events for the aqara cube: shake, +wakeup, fall, tap, slide, flip180, flip90, rotate_left and +rotate_right. Every event has additional information, such as the sides +involved, or the degrees turned.</p> +<p>Now you are ready to set up home assistant support in zigbee2mqtt +following <a class="link" href="https://koenkk.github.io/zigbee2mqtt/integration/home_assistant.html" target="_blank" rel="noopener" +>this +guide</a>.</p>Progress bars in pythonhttps://balkian.com/p/progress-bars-in-python/Wed, 28 Sep 2016 18:47:00 +0000https://balkian.com/p/progress-bars-in-python/<p><a class="link" href="https://github.com/noamraph/tqdm" target="_blank" rel="noopener" +>tqdm</a> is a nice way to add progress +bars in the command line or in a jupyter notebook.</p> +<p><img src="https://camo.githubusercontent.com/48838faaa8d00ea297f18e5bf55d3c6bb4e0ba6b/68747470733a2f2f692e696d6775722e636f6d2f686539417735432e676966" +loading="lazy" +alt="image" +></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">tqdm</span> <span class="kn">import</span> <span class="n">tqdm</span> +</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)):</span> +</span></span><span class="line"><span class="cl"> <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Sharing dotfileshttps://balkian.com/p/sharing-dotfiles/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/p/sharing-dotfiles/<p>Today&rsquo;s post is half a quick note, half public shaming. In other words, +it is a reminder to be very careful with OAuth tokens and passwords.</p> +<p>As part of moving to emacs, I starting using the incredibly useful +<a class="link" href="https://github.com/defunkt/gist.el" target="_blank" rel="noopener" +>gh.el</a>. When you first use it, the +extension saves either your password or an OAuth token in your +.gitconfig file. This is cool and convenient, unless you <a class="link" href="https://github.com/balkian/dotfiles" target="_blank" rel="noopener" +>happen to be +publishing your .gitconfig file in a public +repo</a>.</p> +<p>So, how can you still share your gitconfig without sharing your +password/token with the rest of the world? Since Git 1.7.0, you can +<a class="link" href="http://stackoverflow.com/questions/1557183/is-it-possible-to-include-a-file-in-your-gitconfig" target="_blank" rel="noopener" +>include other files in your +gitconfig</a>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[include] +</span></span><span class="line"><span class="cl"> path = ~/.gitconfig_secret +</span></span></code></pre></td></tr></table> +</div> +</div><p>And now, in your .gitconfig_secret file, you just have to add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[github] +</span></span><span class="line"><span class="cl"> user = balkian +</span></span><span class="line"><span class="cl"> token = &#34;&lt; Your secret token &gt;&#34; +</span></span></code></pre></td></tr></table> +</div> +</div>Zoterohttps://balkian.com/p/zotero/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/p/zotero/<p><a class="link" href="https://www.zotero.org/" target="_blank" rel="noopener" +>Zotero</a> is an Open Source tool that lets you +organise your bibliography, syncing it with the cloud. Unlike other +alternatives such as <a class="link" href="http://www.mendeley.com" target="_blank" rel="noopener" +>Mendeley</a>, Zotero can +upload the attachments and data to a private cloud via WebDav.</p> +<p>If you use nginx as your web server, know that even though it provides +partial support for webdav, Zotero needs more than that. Hence, you will +need another webdav server, and optionally let nginx proxy to it. This +short post provides the basics to get that set-up working under +Debian/Ubuntu.</p> +<h2 id="setting-up-apache">Setting up Apache +</h2><p>First we need to install Apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install apache2 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Change the head of &ldquo;/etc/apache2/sites-enabled/000-default&rdquo; to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;VirtualHost</span> <span class="s">*:880</span><span class="nt">&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Then, create a file /etc/apache2/sites-available/webdav:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nb">Alias</span> <span class="sx">/dav</span> <span class="sx">/home/webdav/dav</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/dav</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">on</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Order</span> Allow,Deny +</span></span><span class="line"><span class="cl"> <span class="nb">Allow</span> from <span class="k">all</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">On</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Options</span> +Indexes +</span></span><span class="line"><span class="cl"> <span class="nb">AuthType</span> Basic +</span></span><span class="line"><span class="cl"> <span class="nb">AuthName</span> DAV +</span></span><span class="line"><span class="cl"> <span class="nb">AuthBasicProvider</span> file +</span></span><span class="line"><span class="cl"> <span class="nb">AuthUserFile</span> <span class="sx">/home/webdav/.htpasswd</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Require</span> valid-user +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Ideally, you want your webdav folders to be private, adding +authentication to them. So you need to create the webdav and zotero +users and add the passwords to an htpasswd file. Even though you could +use a single user, since you will be configuring several clients with +your credentials I encourage you to create the zotero user as well. This +way you can always change the password for zotero without affecting any +other application using webdav.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo adduser webdav +</span></span><span class="line"><span class="cl">sudo htpasswd -c /home/webdav/.htpasswd webdav +</span></span><span class="line"><span class="cl">sudo htpasswd /home/webdav/.htpasswd zotero +</span></span><span class="line"><span class="cl">sudo mkdir -p /home/webdav/dav/zotero +</span></span></code></pre></td></tr></table> +</div> +</div><p>Enable the site and restart apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo a2enmod webdav +</span></span><span class="line"><span class="cl">sudo a2enmod dav_fs +</span></span><span class="line"><span class="cl">sudo a2ensite webdav +</span></span><span class="line"><span class="cl">sudo service apache2 restart +</span></span></code></pre></td></tr></table> +</div> +</div><p>At this point everything should be working at +<a class="link" href="http://" target="_blank" rel="noopener" +>http://</a>&lt;your_host&gt;:880/dav/zotero</p> +<h2 id="setting-up-nginx">Setting up NGINX +</h2><p>After the Apache side is working, we can use nginx as a proxy to get +cleaner URIs. In your desired site/location, add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/dav</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="kn">client_max_body_size</span> <span class="s">20M</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:880</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now just reload nginx:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo service nginx force-reload +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extras">Extras +</h2><ul> +<li><a class="link" href="http://zoteroreader.com/" target="_blank" rel="noopener" +>Zotero Reader</a> - HTML5 client</li> +<li><a class="link" href="https://github.com/ajlyon/zandy" target="_blank" rel="noopener" +>Zandy</a> - Android Open Source +client</li> +</ul>Proxies with Apache and pythonhttps://balkian.com/p/proxies-with-apache-and-python/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/p/proxies-with-apache-and-python/<p>This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name <em>application</em>. Hence, wsgi:application.</p> +<h2 id="gunicorn">Gunicorn +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPass</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPassReverse</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">RequestHeader</span> set SCRIPT_NAME <span class="s2">&#34;/myapp/&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p><strong>Important</strong>: <em>SCRIPT_NAME</em> and the end of <em>ProxyPass</em> URL <strong>MUST BE +THE SAME</strong>. Otherwise, Gunicorn will fail miserably.</p> +<p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">venv/bin/gunicorn -w <span class="m">4</span> -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="uwsgi">UWSGI +</h2><p>This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">SetHandler</span> uwsgi_handler +</span></span><span class="line"><span class="cl"> <span class="nb">uWSGISocker</span> <span class="m">127.0.0.1</span>:8888 +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uwsgi --socket 127.0.0.1:8888 -w wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extra-supervisor">Extra: Supervisor +</h2><p>If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[unix_http_server]</span> +</span></span><span class="line"><span class="cl"><span class="na">file</span><span class="o">=</span><span class="s">/tmp/myapp.sock ; path to your socket file</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisord]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisor.log</span> +</span></span><span class="line"><span class="cl"><span class="na">childlogdir</span> <span class="o">=</span> <span class="s">%(here)s/logs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[rpcinterface:supervisor]</span> +</span></span><span class="line"><span class="cl"><span class="na">supervisor.rpcinterface_factory</span> <span class="o">=</span> <span class="s">supervisor.rpcinterface:make_main_rpcinterface</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisorctl]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisorctl.log</span> +</span></span><span class="line"><span class="cl"><span class="na">serverurl</span><span class="o">=</span><span class="s">unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[program:myapp]</span> +</span></span><span class="line"><span class="cl"><span class="na">command</span> <span class="o">=</span> <span class="s">venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application</span> +</span></span><span class="line"><span class="cl"><span class="na">directory</span> <span class="o">=</span> <span class="s">%(here)s</span> +</span></span><span class="line"><span class="cl"><span class="na">environment</span> <span class="o">=</span> <span class="s">PATH=%(here)s/venv/bin/</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/myapp.log</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Publishing on PyPihttps://balkian.com/p/publishing-on-pypi/Sat, 27 Sep 2014 10:00:00 +0000https://balkian.com/p/publishing-on-pypi/<p>Developing a python module and publishing it on Github is cool, but most +of the times you want others to download and use it easily. That is the +role of PyPi, the python package repository. In this post I show you how +to publish your package in less than 10 minutes.</p> +<h2 id="choose-a-fancy-name">Choose a fancy name +</h2><p>If you haven&rsquo;t done so yet, take a minute or two to think about this. +To publish on PyPi you need a name for your package that isn&rsquo;t taken. +What&rsquo;s more, a catchy and unique name will help people remember your +module and feel more inclined to at least try it.</p> +<p>The package name should hint what your module does, but that&rsquo;s not +always the case. That&rsquo;s your call. I personally put uniqueness and +memorability over describing the functionality.</p> +<h2 id="create-a-pypirc-configuration-file">Create a .pypirc configuration file +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">[distutils] # this tells distutils what package indexes you can push to</span> +</span></span><span class="line"><span class="cl"><span class="na">index-servers</span> <span class="o">=</span><span class="s"> +</span></span></span><span class="line"><span class="cl"><span class="s"> pypi # the live PyPI +</span></span></span><span class="line"><span class="cl"><span class="s"> pypitest # test PyPI</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypi] # authentication details for live PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://pypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span><span class="line"><span class="cl"><span class="na">password</span> <span class="o">=</span> <span class="s">{ your_password } # not necessary</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypitest] # authentication details for test PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://testpypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>As you can see, you need to register both in the <a class="link" href="https://pypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>main pypi +repository</a> and +the <a class="link" href="https://testpypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>testing +server</a>. The +usernames and passwords might be different, that is up to you!</p> +<h2 id="prepare-your-package">Prepare your package +</h2><p>This should be the structure:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">root-dir/ # Any name you want +</span></span><span class="line"><span class="cl"> setup.py +</span></span><span class="line"><span class="cl"> setup.cfg +</span></span><span class="line"><span class="cl"> LICENSE.txt +</span></span><span class="line"><span class="cl"> README.md +</span></span><span class="line"><span class="cl"> mypackage/ +</span></span><span class="line"><span class="cl"> __init__.py +</span></span><span class="line"><span class="cl"> foo.py +</span></span><span class="line"><span class="cl"> bar.py +</span></span><span class="line"><span class="cl"> baz.py +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="setupcfg">setup.cfg +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[metadata]</span> +</span></span><span class="line"><span class="cl"><span class="na">description-file</span> <span class="o">=</span> <span class="s">README.md</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The markdown README is the <em>de facto</em> standard in Github, but you can +also use rST (reStructuredText), the standard in the python community.</p> +<h3 id="setuppy">setup.py +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">distutils.core</span> <span class="kn">import</span> <span class="n">setup</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">setup</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;mypackage&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;mypackage&#39;</span><span class="p">],</span> <span class="c1"># this must be the same as the name above</span> +</span></span><span class="line"><span class="cl"> <span class="n">version</span> <span class="o">=</span> <span class="s1">&#39;{ version }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">description</span> <span class="o">=</span> <span class="s1">&#39;{ description }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">author</span> <span class="o">=</span> <span class="s1">&#39;{ name }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">email</span> <span class="o">=</span> <span class="s1">&#39;{ email }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{package}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="c1"># URL to the github repo</span> +</span></span><span class="line"><span class="cl"> <span class="n">download_url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{repo}</span><span class="s1">/tarball/</span><span class="si">{version}</span><span class="s1">&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">keywords</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;websockets&#39;</span><span class="p">,</span> <span class="s1">&#39;display&#39;</span><span class="p">,</span> <span class="s1">&#39;d3&#39;</span><span class="p">],</span> <span class="c1"># list of keywords that represent your package</span> +</span></span><span class="line"><span class="cl"> <span class="n">classifiers</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>You might notice that the download_url points to a Github URL. We could +host our package anywhere, but Github is a convenient option. To create +the tarball and the zip packages, you only need to tag a tag in your +repository and push it to github:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git tag <span class="o">{</span>version<span class="o">}</span> -m <span class="s2">&#34;{ Description of this tag/version}&#34;</span> +</span></span><span class="line"><span class="cl">git push --tags origin master +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="push-to-the-testingmain-pypi-server">Push to the testing/main pypi server +</h2><p>It is advisable that you try your package on the test repository and fix +any problems first. The process is simple:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">python setup.py register -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> python setup.py sdist upload -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>If everything went as expected, you can now install your package through +pip and browse your package&rsquo;s page. For instance, check my senpy +package: <a class="link" href="https://pypi.python.org/pypi/senpy" target="_blank" rel="noopener" +>https://pypi.python.org/pypi/senpy</a></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install senpy +</span></span></code></pre></td></tr></table> +</div> +</div>Updating EuroLoveMaphttps://balkian.com/p/updating-eurolovemap/Thu, 27 Mar 2014 14:00:00 +0000https://balkian.com/p/updating-eurolovemap/<p>As part of the <a class="link" href="http://www.opener-project.org/2013/07/18/opener-hackathon-in-amsterdam/" target="_blank" rel="noopener" +>OpeNER +hackathon</a> +we decided to build a prototype that would allow us to compare how +different countries feel about several topics. We used the OpeNER +pipeline to get the sentiment from a set of newspaper articles we +gathered from media in several languages. Then we aggregated those +articles by category and country (using the source of the article or the +language it was written in), obtaining the &ldquo;overall feeling&rdquo; of each +country about each topic. Then, we used some fancy JavaScript to make +sense out of the raw information.</p> +<p>It didn&rsquo;t go too bad, it turns out <a class="link" href="http://eurosentiment.eu/wp-content/uploads/2013/07/BOLv9qnCIAAJEek.jpg" target="_blank" rel="noopener" +>we +won</a>.</p> +<p>Now, it was time for a face-lift. I used this opportunity to play with +new technologies and improve it:</p> +<ul> +<li>Using Flask, this time using python 3.3 and Bootstrap 3.0</li> +<li>Cool HTML5+JS cards (thanks to +<a class="link" href="http://pastetophone.com" target="_blank" rel="noopener" +>pastetophone</a>)</li> +<li>Automatic generation of fake personal data to test the interface</li> +<li>Obfuscation of personal emails</li> +</ul> +<p>The result can be <a class="link" href="http://eurolovemap.herokuapp.com/" target="_blank" rel="noopener" +>seen here</a>.</p> +<h2 id="publishing-a-python-3-app-on-heroku">Publishing a Python 3 app on Heroku +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkvirtualenv -p /usr/bin/python3.3 eurolovemap +</span></span></code></pre></td></tr></table> +</div> +</div><p>Since Heroku uses python 2.7 by default, we have to tell it which +version we want, although it supports python 3.4 as well. I couldn&rsquo;t +get python 3.4 working using the +<a class="link" href="https://launchpad.net/~fkrull/&#43;archive/deadsnakes" target="_blank" rel="noopener" +>deadsnakes</a> ppa, so +I used python 3.3 instead, which works fine but is not officially +supported. Just create a file named <em>runtime.txt</em> in your project root, +with the python version you want to use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python-3.3.1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Don&rsquo;t forget to freeze your dependencies so Heroku can install them: +<code>bash pip freze &gt; requirements.txt</code></p> +<h2 id="publishing-personal-emails">Publishing personal emails +</h2><p>There are really sophisticated and effective ways to obfuscate personal +emails so that spammers cannot easily grab yours. However, this time I +needed something really simple to hide our emails from the simplest form +of crawlers. Most of the team are in academia somehow, so in the end all +our emails are available in sites like Google Scholar. Anyway, nobody +likes getting spammed so I settled for a custom <a class="link" href="http://en.wikipedia.org/wiki/Caesar_cipher" target="_blank" rel="noopener" +>Caesar +cipher</a>. Please, don&rsquo;t use +it for any serious application if you are concerned about being spammed.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">blur_email</span><span class="p">(</span><span class="n">email</span><span class="p">):</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">email</span><span class="p">])</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>And this is the client side:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">&#39;profile-email&#39;</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">e</span> <span class="k">in</span> <span class="nx">elems</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">blur</span> <span class="o">=</span> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">email</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">s</span> <span class="k">in</span> <span class="nx">blur</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">blur</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="o">+</span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="nx">a</span><span class="o">-</span><span class="mi">5</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Unfortunately, this approach does not hide your email from anyone using +<a class="link" href="http://phantomjs.org/" target="_blank" rel="noopener" +>PhantomJS</a>, +<a class="link" href="http://zombie.labnotes.org/" target="_blank" rel="noopener" +>ZombieJS</a> or similar. For that, other +approaches like generating a picture with the address would be +necessary. Nevertheless, it is overkill for a really simple ad-hoc +application with custom formatting and just a bunch of emails that would +easily be grabbed manually.</p> +<h2 id="generation-of-fake-data">Generation of fake data +</h2><p>To test the contact section of the site, I wanted to populate it with +fake data. <a class="link" href="https://github.com/joke2k/faker" target="_blank" rel="noopener" +>Fake-Factory</a> is an amazing +library that can generate fake data of almost any kind: emails, +association names, acronyms&hellip; It even lets you localise the results +(get Spanish names, for instance) and generate factories for certain +classes (à la Django).</p> +<p>But I also wanted pictures, enter <a class="link" href="http://lorempixel.com/" target="_blank" rel="noopener" +>Lorem Pixel</a>. +With its API you can generate pictures of almost any size, for different +topics (e.g. nightlife, people) and with a custom text. You can even use +an index, so it will always show the same picture.</p> +<p>For instance, the picture below is served through Lorem Pixel.</p> +<p><img src="http://lorempixel.com/400/200/nightlife/" +loading="lazy" +></p> +<p>By the way, if you only want cat pictures, take a look at +<a class="link" href="http://placekitten.com/" target="_blank" rel="noopener" +>Placekitten</a>. And for NSFW text, there&rsquo;s the +<a class="link" href="http://slipsum.com/" target="_blank" rel="noopener" +>Samuel L. Jackson Ipsum</a></p>Remove git files with globbinghttps://balkian.com/p/remove-git-files-with-globbing/Thu, 22 Aug 2013 23:14:00 +0000https://balkian.com/p/remove-git-files-with-globbing/<p>A simple trick. If you want to remove all the &lsquo;.swp&rsquo; files from a git +repository, just use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git rm --cached <span class="s1">&#39;**.swp&#39;</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Creating my webhttps://balkian.com/p/creating-my-web/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/p/creating-my-web/<p>I&rsquo;ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +<a class="link" href="http://jekyllrb.com" target="_blank" rel="noopener" +>Jekyll</a> will automatically generate static pages +for my posts every time I commit anything new to this repository.</p> +<p>But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.</p> +<p>I hadn&rsquo;t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven&rsquo;t included +any Javascript code in the page. Probably I will use some to add my +<a class="link" href="http://gist.github.com/balkian" target="_blank" rel="noopener" +>gists</a> and +<a class="link" href="http://github.com/balkian" target="_blank" rel="noopener" +>repositories</a>, but we will see about that.</p> +<p>I think the code speaks for itself, so you can check out <a class="link" href="http://github.com/balkian/balkian.github.com" target="_blank" rel="noopener" +>my repository +on Github</a>. You can clone +and deploy it easily like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone +</span></span><span class="line"><span class="cl">https://github.com/balkian/balkian.github.com <span class="nb">cd</span> balkian.github.com +</span></span><span class="line"><span class="cl">jekyll serve -w +</span></span></code></pre></td></tr></table> +</div> +</div><p>I will keep updating this post with information about:</p> +<ul> +<li>Some Jekyll plugins that might be useful</li> +<li>What CSS tricks I learnt</li> +<li>The webfonts I used</li> +<li>The badge on the left side of the page</li> +</ul>Emacs: show plain text versionhttps://balkian.com/p/emacs-show-plain-text-version/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/emacs-show-plain-text-version/<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">font-lock-mode</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Fixing HDMI flickeringhttps://balkian.com/p/fixing-hdmi-flickering/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/fixing-hdmi-flickering/<img src="https://balkian.com/img/rpi.png" alt="Featured image of post Fixing HDMI flickering" /><p>Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_drive</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_group</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_mode</span><span class="o">=</span><span class="mi">42</span> +</span></span><span class="line"><span class="cl"> <span class="n">disable_overscan</span><span class="o">=</span><span class="mi">1</span> +</span></span><span class="line"><span class="cl"> <span class="n">config_hdmi_boost</span><span class="o">=</span><span class="mi">7</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/post/page/1/index.html b/post/page/1/index.html new file mode 100644 index 0000000..e4423a3 --- /dev/null +++ b/post/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/post/ + \ No newline at end of file diff --git a/post/page/2/index.html b/post/page/2/index.html new file mode 100644 index 0000000..4b8cbb8 --- /dev/null +++ b/post/page/2/index.html @@ -0,0 +1,36 @@ +Posts +

Section

20 pages

Posts

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/post/page/3/index.html b/post/page/3/index.html new file mode 100644 index 0000000..36bcc96 --- /dev/null +++ b/post/page/3/index.html @@ -0,0 +1,36 @@ +Posts +

Section

20 pages

Posts

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/post/page/4/index.html b/post/page/4/index.html new file mode 100644 index 0000000..ccf12ff --- /dev/null +++ b/post/page/4/index.html @@ -0,0 +1,36 @@ +Posts +

Section

20 pages

Posts

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/post/scripts/pxe.sh b/post/scripts/pxe.sh new file mode 100644 index 0000000..ea766f8 --- /dev/null +++ b/post/scripts/pxe.sh @@ -0,0 +1,9 @@ +#/bin/bash +USER=j +IFNAME=enp62s0u1u3 +BINARY=test-tftp.bin +ip address flush dev $IFNAME +ip address add 10.1.1.10/24 dev $IFNAME +dnsmasq -i $IFNAME --dhcp-range=10.1.1.50,10.1.1.100 \ + --dhcp-boot=$BINARY \ + --enable-tftp --tftp-root=/home/$USER/Downloads/pxe -d -u $USER -p0 -K --log-dhcp --bootp-dynamic diff --git a/projects/index.html b/projects/index.html new file mode 100644 index 0000000..a1029b6 --- /dev/null +++ b/projects/index.html @@ -0,0 +1,21 @@ +Projects +

Projects

Ongoing Projects

  • Soil: an agent-based simulator for social networks based on nx-sim and networkx.
  • Soilent: an efficient scheduler for soil using rust and pyo3.
  • Senpy: a framework for semantic sentiment and emotion analysis services.

Past Projects

  • Onyx: an ontology for emotion analysis that includes concepts from W3C’s provenance.
  • ESP8266 Clock NTP: a simple clock display using arduino, the ESP8266 and NTP (network time protocol).
  • Shine ESP: control an ws2812b LED strip over the network with an ESP8266.
  • Bitter: a wrapper and CLI over the (now defunct) Twitter API to researchers to download Twitter data much faster using multiple accounts.
  • Marl: I updated this ontology, originally created by Adam Westerski, to make it compatible with the W3C’s provenance ontology.
  • Hermes: one of my first projects, developed together with David Pérez as the special custom assignment in one of our courses. Hermes is an affective bot designed to mimic the behavour of humans. It included a plug-in system for its sensors and actuators. The information from its sensors changed its emotional state, which was shown via its actuators. Among others, it could fetch inforation from Twitter or its host system and change the expressions of an external Face made with servo motors or speak via its Text-To-Speech software. For instance, it could detect it was running out of battery, showing a sad face and sending an alerting tweet. You can see it in action in these two youtube videos: Part 1 and Part 2.
  • Maia: the Modular Architecture for Intelligent Agents is an evented agent architecture that aims to update the classical frameworks for intelligent agents with the concepts emerged from the Live Web.
  • EESTEC.net: the Plone based official portal of EESTEC. I fixed some bugs and implemented basic features.

For more information, check my list of public repositories in Github.

+Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/scss/style.min.314a81d1fef50606da5df138ce819c12f9ed0c4f2487c1964bccb4c3cc737879.css b/scss/style.min.314a81d1fef50606da5df138ce819c12f9ed0c4f2487c1964bccb4c3cc737879.css new file mode 100644 index 0000000..2d0a230 --- /dev/null +++ b/scss/style.min.314a81d1fef50606da5df138ce819c12f9ed0c4f2487c1964bccb4c3cc737879.css @@ -0,0 +1,10 @@ +/*!* Hugo Theme Stack +* +* @author: Jimmy Cai +* @website: https://jimmycai.com +* @link: https://github.com/CaiJimmy/hugo-theme-stack*/:root{--main-top-padding:35px;--body-background:#f5f5fa;--accent-color:#34495e;--accent-color-darker:#2c3e50;--accent-color-text:#fff;--body-text-color:#707070;--tag-border-radius:4px;--section-separation:40px;--scrollbar-thumb:hsl(0, 0%, 85%);--scrollbar-track:var(--body-background)}@media(min-width:1280px){:root{--main-top-padding:50px}}:root[data-scheme=dark]{--body-background:#303030;--accent-color:#ecf0f1;--accent-color-darker:#bdc3c7;--accent-color-text:#000;--body-text-color:rgba(255, 255, 255, 0.7);--scrollbar-thumb:hsl(0, 0%, 40%);--scrollbar-track:var(--body-background)}:root{--sys-font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", "Droid Sans", "Helvetica Neue";--zh-font-family:"PingFang SC", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei";--base-font-family:"Lato", var(--sys-font-family), var(--zh-font-family), sans-serif;--code-font-family:Menlo, Monaco, Consolas, "Courier New", var(--zh-font-family), monospace}:root{--card-background:#fff;--card-background-selected:#eaeaea;--card-text-color-main:#000;--card-text-color-secondary:#747474;--card-text-color-tertiary:#767676;--card-separator-color:rgba(218, 218, 218, 0.5);--card-border-radius:10px;--card-padding:20px;--small-card-padding:25px 20px}@media(min-width:768px){:root{--card-padding:25px}}@media(min-width:1280px){:root{--card-padding:30px}}@media(min-width:768px){:root{--small-card-padding:25px}}:root[data-scheme=dark]{--card-background:#424242;--card-background-selected:rgba(255, 255, 255, 0.16);--card-text-color-main:rgba(255, 255, 255, 0.9);--card-text-color-secondary:rgba(255, 255, 255, 0.7);--card-text-color-tertiary:rgba(255, 255, 255, 0.5);--card-separator-color:rgba(255, 255, 255, 0.12)}:root{--article-font-family:var(--base-font-family);--article-font-size:1.6rem;--article-line-height:1.85}@media(min-width:768px){:root{--article-font-size:1.7rem}}:root{--blockquote-border-size:4px;--blockquote-background-color:rgb(248 248 248);--heading-border-size:4px;--link-background-color:189, 195, 199;--link-background-opacity:0.5;--link-background-opacity-hover:0.7;--pre-background-color:#272822;--pre-text-color:#f8f8f2;--code-background-color:rgba(0, 0, 0, 0.12);--code-text-color:#808080;--table-border-color:#dadada;--tr-even-background-color:#efefee;--kbd-border-color:#dadada}:root[data-scheme=dark]{--code-background-color:#272822;--code-text-color:rgba(255, 255, 255, 0.9);--table-border-color:#717171;--tr-even-background-color:#545454;--blockquote-background-color:rgb(75 75 75)}:root{--shadow-l1:0px 4px 8px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);--shadow-l2:0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);--shadow-l3:0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);--shadow-l4:0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), + 0px 0px 1px rgba(0, 0, 0, 0.04)}[data-scheme=light]{--pre-text-color:#272822;--pre-background-color:#fafafa}[data-scheme=light] .chroma{color:#272822;background-color:#fafafa}[data-scheme=light] .chroma .err{color:#960050}[data-scheme=light] .chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}[data-scheme=light] .chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:100%;display:block}[data-scheme=light] .chroma .lntable>tbody{display:block;width:100%}[data-scheme=light] .chroma .lntable>tbody>tr{display:flex;width:100%}[data-scheme=light] .chroma .lntable>tbody>tr>td:last-child{overflow-x:auto}[data-scheme=light] .chroma .hl{display:block;width:100%;background-color:#ffc}[data-scheme=light] .chroma .lnt{margin-right:.4em;padding:0 .4em;color:#7f7f7f;display:block}[data-scheme=light] .chroma .ln{margin-right:.4em;padding:0 .4em;color:#7f7f7f}[data-scheme=light] .chroma .k{color:#00a8c8}[data-scheme=light] .chroma .kc{color:#00a8c8}[data-scheme=light] .chroma .kd{color:#00a8c8}[data-scheme=light] .chroma .kn{color:#f92672}[data-scheme=light] .chroma .kp{color:#00a8c8}[data-scheme=light] .chroma .kr{color:#00a8c8}[data-scheme=light] .chroma .kt{color:#00a8c8}[data-scheme=light] .chroma .n{color:#111}[data-scheme=light] .chroma .na{color:#75af00}[data-scheme=light] .chroma .nb{color:#111}[data-scheme=light] .chroma .bp{color:#111}[data-scheme=light] .chroma .nc{color:#75af00}[data-scheme=light] .chroma .no{color:#00a8c8}[data-scheme=light] .chroma .nd{color:#75af00}[data-scheme=light] .chroma .ni{color:#111}[data-scheme=light] .chroma .ne{color:#75af00}[data-scheme=light] .chroma .nf{color:#75af00}[data-scheme=light] .chroma .fm{color:#111}[data-scheme=light] .chroma .nl{color:#111}[data-scheme=light] .chroma .nn{color:#111}[data-scheme=light] .chroma .nx{color:#75af00}[data-scheme=light] .chroma .py{color:#111}[data-scheme=light] .chroma .nt{color:#f92672}[data-scheme=light] .chroma .nv{color:#111}[data-scheme=light] .chroma .vc{color:#111}[data-scheme=light] .chroma .vg{color:#111}[data-scheme=light] .chroma .vi{color:#111}[data-scheme=light] .chroma .vm{color:#111}[data-scheme=light] .chroma .l{color:#ae81ff}[data-scheme=light] .chroma .ld{color:#d88200}[data-scheme=light] .chroma .s{color:#d88200}[data-scheme=light] .chroma .sa{color:#d88200}[data-scheme=light] .chroma .sb{color:#d88200}[data-scheme=light] .chroma .sc{color:#d88200}[data-scheme=light] .chroma .dl{color:#d88200}[data-scheme=light] .chroma .sd{color:#d88200}[data-scheme=light] .chroma .s2{color:#d88200}[data-scheme=light] .chroma .se{color:#ae81ff}[data-scheme=light] .chroma .sh{color:#d88200}[data-scheme=light] .chroma .si{color:#d88200}[data-scheme=light] .chroma .sx{color:#d88200}[data-scheme=light] .chroma .sr{color:#d88200}[data-scheme=light] .chroma .s1{color:#d88200}[data-scheme=light] .chroma .ss{color:#d88200}[data-scheme=light] .chroma .m{color:#ae81ff}[data-scheme=light] .chroma .mb{color:#ae81ff}[data-scheme=light] .chroma .mf{color:#ae81ff}[data-scheme=light] .chroma .mh{color:#ae81ff}[data-scheme=light] .chroma .mi{color:#ae81ff}[data-scheme=light] .chroma .il{color:#ae81ff}[data-scheme=light] .chroma .mo{color:#ae81ff}[data-scheme=light] .chroma .o{color:#f92672}[data-scheme=light] .chroma .ow{color:#f92672}[data-scheme=light] .chroma .p{color:#111}[data-scheme=light] .chroma .c{color:#75715e}[data-scheme=light] .chroma .ch{color:#75715e}[data-scheme=light] .chroma .cm{color:#75715e}[data-scheme=light] .chroma .c1{color:#75715e}[data-scheme=light] .chroma .cs{color:#75715e}[data-scheme=light] .chroma .cp{color:#75715e}[data-scheme=light] .chroma .cpf{color:#75715e}[data-scheme=light] .chroma .gd{color:#f92672}[data-scheme=light] .chroma .ge{font-style:italic}[data-scheme=light] .chroma .gi{color:#75af00}[data-scheme=light] .chroma .gs{font-weight:700}[data-scheme=light] .chroma .gu{color:#75715e}[data-scheme=dark]{--pre-text-color:#f8f8f2;--pre-background-color:#272822}[data-scheme=dark] .chroma{color:#f8f8f2;background-color:#272822}[data-scheme=dark] .chroma .err{color:#bb0064}[data-scheme=dark] .chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}[data-scheme=dark] .chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:100%;display:block}[data-scheme=dark] .chroma .lntable>tbody{display:block;width:100%}[data-scheme=dark] .chroma .lntable>tbody>tr{display:flex;width:100%}[data-scheme=dark] .chroma .lntable>tbody>tr>td:last-child{overflow-x:auto}[data-scheme=dark] .chroma .hl{display:block;width:100%;background-color:#ffc}[data-scheme=dark] .chroma .lnt{margin-right:.4em;padding:0 .4em;color:#7f7f7f;display:block}[data-scheme=dark] .chroma .ln{margin-right:.4em;padding:0 .4em;color:#7f7f7f}[data-scheme=dark] .chroma .k{color:#66d9ef}[data-scheme=dark] .chroma .kc{color:#66d9ef}[data-scheme=dark] .chroma .kd{color:#66d9ef}[data-scheme=dark] .chroma .kn{color:#f92672}[data-scheme=dark] .chroma .kp{color:#66d9ef}[data-scheme=dark] .chroma .kr{color:#66d9ef}[data-scheme=dark] .chroma .kt{color:#66d9ef}[data-scheme=dark] .chroma .n{color:#f8f8f2}[data-scheme=dark] .chroma .na{color:#a6e22e}[data-scheme=dark] .chroma .nb{color:#f8f8f2}[data-scheme=dark] .chroma .bp{color:#f8f8f2}[data-scheme=dark] .chroma .nc{color:#a6e22e}[data-scheme=dark] .chroma .no{color:#66d9ef}[data-scheme=dark] .chroma .nd{color:#a6e22e}[data-scheme=dark] .chroma .ni{color:#f8f8f2}[data-scheme=dark] .chroma .ne{color:#a6e22e}[data-scheme=dark] .chroma .nf{color:#a6e22e}[data-scheme=dark] .chroma .fm{color:#f8f8f2}[data-scheme=dark] .chroma .nl{color:#f8f8f2}[data-scheme=dark] .chroma .nn{color:#f8f8f2}[data-scheme=dark] .chroma .nx{color:#a6e22e}[data-scheme=dark] .chroma .py{color:#f8f8f2}[data-scheme=dark] .chroma .nt{color:#f92672}[data-scheme=dark] .chroma .nv{color:#f8f8f2}[data-scheme=dark] .chroma .vc{color:#f8f8f2}[data-scheme=dark] .chroma .vg{color:#f8f8f2}[data-scheme=dark] .chroma .vi{color:#f8f8f2}[data-scheme=dark] .chroma .vm{color:#f8f8f2}[data-scheme=dark] .chroma .l{color:#ae81ff}[data-scheme=dark] .chroma .ld{color:#e6db74}[data-scheme=dark] .chroma .s{color:#e6db74}[data-scheme=dark] .chroma .sa{color:#e6db74}[data-scheme=dark] .chroma .sb{color:#e6db74}[data-scheme=dark] .chroma .sc{color:#e6db74}[data-scheme=dark] .chroma .dl{color:#e6db74}[data-scheme=dark] .chroma .sd{color:#e6db74}[data-scheme=dark] .chroma .s2{color:#e6db74}[data-scheme=dark] .chroma .se{color:#ae81ff}[data-scheme=dark] .chroma .sh{color:#e6db74}[data-scheme=dark] .chroma .si{color:#e6db74}[data-scheme=dark] .chroma .sx{color:#e6db74}[data-scheme=dark] .chroma .sr{color:#e6db74}[data-scheme=dark] .chroma .s1{color:#e6db74}[data-scheme=dark] .chroma .ss{color:#e6db74}[data-scheme=dark] .chroma .m{color:#ae81ff}[data-scheme=dark] .chroma .mb{color:#ae81ff}[data-scheme=dark] .chroma .mf{color:#ae81ff}[data-scheme=dark] .chroma .mh{color:#ae81ff}[data-scheme=dark] .chroma .mi{color:#ae81ff}[data-scheme=dark] .chroma .il{color:#ae81ff}[data-scheme=dark] .chroma .mo{color:#ae81ff}[data-scheme=dark] .chroma .o{color:#f92672}[data-scheme=dark] .chroma .ow{color:#f92672}[data-scheme=dark] .chroma .p{color:#f8f8f2}[data-scheme=dark] .chroma .c{color:#75715e}[data-scheme=dark] .chroma .ch{color:#75715e}[data-scheme=dark] .chroma .cm{color:#75715e}[data-scheme=dark] .chroma .c1{color:#75715e}[data-scheme=dark] .chroma .cs{color:#75715e}[data-scheme=dark] .chroma .cp{color:#75715e}[data-scheme=dark] .chroma .cpf{color:#75715e}[data-scheme=dark] .chroma .gd{color:#f92672}[data-scheme=dark] .chroma .ge{font-style:italic}[data-scheme=dark] .chroma .gi{color:#a6e22e}[data-scheme=dark] .chroma .gs{font-weight:700}[data-scheme=dark] .chroma .gu{color:#75715e}:root{--menu-icon-separation:40px;--container-padding:15px;--widget-separation:var(--section-separation)}.container{margin-left:auto;margin-right:auto}.container .left-sidebar{order:-3;max-width:var(--left-sidebar-max-width)}.container .right-sidebar{order:-1;max-width:var(--right-sidebar-max-width)}@media(min-width:1024px){.container .right-sidebar{display:flex}}@media(min-width:768px){.container.extended{max-width:1024px;--left-sidebar-max-width:25%;--right-sidebar-max-width:30%}}@media(min-width:1024px){.container.extended{max-width:1280px;--left-sidebar-max-width:20%;--right-sidebar-max-width:30%}}@media(min-width:1280px){.container.extended{max-width:1536px;--left-sidebar-max-width:15%;--right-sidebar-max-width:25%}}@media(min-width:768px){.container.compact{--left-sidebar-max-width:25%;max-width:768px}}@media(min-width:1024px){.container.compact{max-width:1024px;--left-sidebar-max-width:20%}}@media(min-width:1280px){.container.compact{max-width:1280px}}.flex{display:flex;flex-direction:row}.flex.column{flex-direction:column}.flex.on-phone--column{flex-direction:column}@media(min-width:768px){.flex.on-phone--column{flex-direction:unset}}.flex .full-width{width:100%}main.main{order:-2;min-width:0;max-width:100%;flex-grow:1;display:flex;flex-direction:column;gap:var(--section-separation)}@media(min-width:768px){main.main{padding-top:var(--main-top-padding)}}.main-container{min-height:100vh;align-items:flex-start;padding:0 15px;gap:var(--section-separation);padding-top:var(--main-top-padding)}@media(min-width:768px){.main-container{padding:0 20px}}/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}/*!* Hamburgers +* @description Tasty CSS-animated hamburgers +* @author Jonathan Suh @jonsuh +* @site https://jonsuh.com/hamburgers +* @link https://github.com/jonsuh/hamburgers*/.hamburger{padding-top:10px;display:inline-block;cursor:pointer;transition-property:opacity,filter;transition-duration:.15s;transition-timing-function:linear;font:inherit;color:inherit;text-transform:none;background-color:transparent;border:0;margin:0;overflow:visible}.hamburger:hover{opacity:.7}.hamburger.is-active:hover{opacity:.7}.hamburger.is-active .hamburger-inner,.hamburger.is-active .hamburger-inner::before,.hamburger.is-active .hamburger-inner::after{background-color:#000}.hamburger-box{width:30px;height:24px;display:inline-block;position:relative}.hamburger-inner{display:block;top:50%;margin-top:-2px}.hamburger-inner,.hamburger-inner::before,.hamburger-inner::after{width:30px;height:2px;background-color:var(--card-text-color-main);border-radius:4px;position:absolute;transition-property:transform;transition-duration:.15s;transition-timing-function:ease}.hamburger-inner::before,.hamburger-inner::after{content:"";display:block}.hamburger-inner::before{top:-10px}.hamburger-inner::after{bottom:-10px}.hamburger--spin .hamburger-inner{transition-duration:.22s;transition-timing-function:cubic-bezier(.55,.055,.675,.19)}.hamburger--spin .hamburger-inner::before{transition:top .1s .25s ease-in,opacity .1s ease-in}.hamburger--spin .hamburger-inner::after{transition:bottom .1s .25s ease-in,transform .22s cubic-bezier(.55,.055,.675,.19)}.hamburger--spin.is-active .hamburger-inner{transform:rotate(225deg);transition-delay:.12s;transition-timing-function:cubic-bezier(.215,.61,.355,1)}.hamburger--spin.is-active .hamburger-inner::before{top:0;opacity:0;transition:top .1s ease-out,opacity .1s .12s ease-out}.hamburger--spin.is-active .hamburger-inner::after{bottom:0;transform:rotate(-90deg);transition:bottom .1s ease-out,transform .22s .12s cubic-bezier(.215,.61,.355,1)}#toggle-menu{background:0 0;border:none;position:absolute;right:0;top:0;z-index:2;cursor:pointer;outline:none}[dir=rtl] #toggle-menu{left:0;right:auto}@media(min-width:768px){#toggle-menu{display:none}}#toggle-menu.is-active .hamburger-inner,#toggle-menu.is-active .hamburger-inner::before,#toggle-menu.is-active .hamburger-inner::after{background-color:var(--accent-color)}#main-menu{list-style:none;overflow-y:auto;flex-grow:1;font-size:1.4rem;background-color:var(--card-background);box-shadow:var(--shadow-l1);display:none;margin:0 calc(var(--container-padding) * -1);padding:30px}@media(min-width:1280px){#main-menu{padding:15px 0}}#main-menu,#main-menu .menu-bottom-section ol{flex-direction:column;gap:30px}@media(min-width:1280px){#main-menu,#main-menu .menu-bottom-section ol{gap:25px}}#main-menu.show{display:flex}@media(min-width:768px){#main-menu{align-items:flex-end;display:flex;background-color:transparent;padding:0;box-shadow:none;margin:0}}#main-menu li{position:relative;vertical-align:middle;padding:0}@media(min-width:768px){#main-menu li{width:100%}}#main-menu li svg{stroke:currentColor;stroke-width:1.33;width:20px;height:20px}#main-menu li a{height:100%;display:inline-flex;align-items:center;color:var(--body-text-color);gap:var(--menu-icon-separation)}#main-menu li span{flex:1}#main-menu li.current a{color:var(--accent-color);font-weight:700}#main-menu li.menu-bottom-section{margin-top:auto}#main-menu li.menu-bottom-section ol{display:flex;padding-left:0}.menu-social{list-style:none;padding:0;margin:0;display:flex;flex-direction:row;gap:10px}.menu-social svg{width:24px;height:24px;stroke:var(--body-text-color);stroke-width:1.33}.article-list{display:flex;flex-direction:column;gap:var(--section-separation)}.article-list article{display:flex;flex-direction:column;background-color:var(--card-background);box-shadow:var(--shadow-l1);border-radius:var(--card-border-radius);overflow:hidden;transition:box-shadow .3s ease}.article-list article:hover{box-shadow:var(--shadow-l2)}.article-list article .article-image img{width:100%;height:150px;object-fit:cover}@media(min-width:768px){.article-list article .article-image img{height:200px}}@media(min-width:1280px){.article-list article .article-image img{height:250px}}.article-list article:nth-child(5n+1) .article-category a{background:#8ea885;color:#fff}.article-list article:nth-child(5n+2) .article-category a{background:#df7988;color:#fff}.article-list article:nth-child(5n+3) .article-category a{background:#0177b8;color:#fff}.article-list article:nth-child(5n+4) .article-category a{background:#ffb900;color:#fff}.article-list article:nth-child(5n+5) .article-category a{background:#6b69d6;color:#fff}.article-details{display:flex;flex-direction:column;justify-content:center;padding:var(--card-padding);gap:15px}.article-title{font-family:var(--article-font-family);font-weight:600;margin:0;color:var(--card-text-color-main);font-size:2.2rem}@media(min-width:1280px){.article-title{font-size:2.4rem}}.article-title a{color:var(--card-text-color-main)}.article-title a:hover{color:var(--card-text-color-main)}.article-subtitle{font-weight:400;color:var(--card-text-color-secondary);line-height:1.5;margin:0;font-size:1.75rem}@media(min-width:1280px){.article-subtitle{font-size:2rem}}.article-title-wrapper{display:flex;flex-direction:column;gap:8px}.article-time,.article-translations{display:flex;color:var(--card-text-color-tertiary);gap:15px}.article-time svg,.article-translations svg{vertical-align:middle;width:20px;height:20px;stroke-width:1.33;flex-shrink:0}.article-time time,.article-time a,.article-translations time,.article-translations a{font-size:1.4rem;color:var(--card-text-color-tertiary)}.article-time>div,.article-translations>div{display:inline-flex;align-items:center;gap:15px}.article-time{flex-wrap:wrap}.article-translations>div{flex-wrap:wrap}.article-category,.article-tags{display:flex;gap:10px;flex-wrap:wrap}.article-category a,.article-tags a{color:var(--accent-color-text);background-color:var(--accent-color);padding:8px 16px;border-radius:var(--tag-border-radius);display:inline-block;font-size:1.4rem;transition:background-color .5s ease}.article-category a:hover,.article-tags a:hover{color:var(--accent-color-text);background-color:var(--accent-color-darker)}.article-list--compact{border-radius:var(--card-border-radius);box-shadow:var(--shadow-l1);background-color:var(--card-background);--image-size:50px}@media(min-width:768px){.article-list--compact{--image-size:60px}}.article-list--compact article>a{display:flex;align-items:center;padding:var(--small-card-padding);gap:15px}.article-list--compact article:not(:last-of-type){border-bottom:1.5px solid var(--card-separator-color)}.article-list--compact article .article-details{flex-grow:1;padding:0;min-height:var(--image-size);gap:10px}.article-list--compact article .article-title{margin:0;font-size:1.6rem}@media(min-width:768px){.article-list--compact article .article-title{font-size:1.8rem}}.article-list--compact article .article-image img{width:var(--image-size);height:var(--image-size);object-fit:cover}.article-list--compact article .article-time{font-size:1.4rem}.article-list--compact article .article-preview{font-size:1.4rem;color:var(--card-text-color-tertiary);margin-top:10px;line-height:1.5}.article-list--tile article{border-radius:var(--card-border-radius);overflow:hidden;position:relative;height:350px;width:250px;box-shadow:var(--shadow-l1);transition:box-shadow .3s ease;background-color:var(--card-background)}.article-list--tile article:hover{box-shadow:var(--shadow-l2)}.article-list--tile article.has-image .article-details{background-color:rgba(0,0,0,.25)}.article-list--tile article.has-image .article-title{color:#fff}.article-list--tile article .article-image{position:absolute;top:0;left:0;width:100%;height:100%}.article-list--tile article .article-image img{width:100%;height:100%;object-fit:cover}.article-list--tile article .article-details{border-radius:var(--card-border-radius);position:relative;height:100%;width:100%;display:flex;flex-direction:column;justify-content:flex-end;z-index:2;padding:15px}@media(min-width:640px){.article-list--tile article .article-details{padding:20px}}.article-list--tile article .article-title{font-size:2rem;font-weight:500;color:var(--card-text-color-main)}@media(min-width:640px){.article-list--tile article .article-title{font-size:2.2rem}}.widget{display:flex;flex-direction:column}.widget .widget-icon svg{width:32px;height:32px;stroke-width:1.6;color:var(--body-text-color)}.tagCloud .tagCloud-tags{display:flex;flex-wrap:wrap;gap:10px}.tagCloud .tagCloud-tags a{background:var(--card-background);box-shadow:var(--shadow-l1);border-radius:var(--tag-border-radius);padding:8px 20px;color:var(--card-text-color-main);font-size:1.4rem;transition:box-shadow .3s ease}.tagCloud .tagCloud-tags a:hover{box-shadow:var(--shadow-l2)}.widget.archives .widget-archive--list{border-radius:var(--card-border-radius);box-shadow:var(--shadow-l1);background-color:var(--card-background)}.widget.archives .archives-year:not(:last-of-type){border-bottom:1.5px solid var(--card-separator-color)}.widget.archives .archives-year a{font-size:1.4rem;padding:18px 25px;display:flex}.widget.archives .archives-year a span.year{flex:1;color:var(--card-text-color-main);font-weight:700}.widget.archives .archives-year a span.count{color:var(--card-text-color-tertiary)}footer.site-footer{padding:20px 0 var(--section-separation);font-size:1.4rem;line-height:1.75}footer.site-footer:before{content:"";display:block;height:3px;width:50px;background:var(--body-text-color);margin-bottom:20px}footer.site-footer .copyright{color:var(--accent-color);font-weight:700;margin-bottom:5px}footer.site-footer .powerby{color:var(--body-text-color);font-weight:400;font-size:1.2rem}footer.site-footer .powerby a{color:var(--body-text-color)}.pagination{display:flex;background-color:var(--card-background);box-shadow:var(--shadow-l1);border-radius:var(--card-border-radius);overflow:hidden;flex-wrap:wrap}.pagination .page-link{padding:16px 32px;display:inline-flex;color:var(--card-text-color-secondary)}.pagination .page-link.current{font-weight:700;background-color:var(--card-background-selected);color:var(--card-text-color-main)}@media(min-width:768px){.sidebar.sticky{position:sticky}}.left-sidebar{display:flex;flex-direction:column;flex-shrink:0;align-self:stretch;gap:var(--sidebar-element-separation);max-width:none;width:100%;position:relative;--sidebar-avatar-size:100px;--sidebar-element-separation:20px;--emoji-size:40px;--emoji-font-size:20px}@media(min-width:768px){.left-sidebar{width:auto;padding-top:var(--main-top-padding);padding-bottom:var(--main-top-padding);max-height:100vh}}@media(min-width:1536px){.left-sidebar{--sidebar-avatar-size:120px;--sidebar-element-separation:25px;--emoji-size:40px}}.left-sidebar.sticky{top:0}.left-sidebar.compact{--sidebar-avatar-size:80px;--emoji-size:30px;--emoji-font-size:15px}@media(min-width:1024px){.left-sidebar.compact header{flex-direction:row}}.left-sidebar.compact header .site-meta{gap:5px}.left-sidebar.compact header .site-name{font-size:1.4rem}@media(min-width:1536px){.left-sidebar.compact header .site-name{font-size:1.75rem}}.left-sidebar.compact header .site-description{font-size:1.4rem}.right-sidebar{width:100%;display:none;flex-direction:column;gap:var(--widget-separation)}.right-sidebar.sticky{top:0}@media(min-width:1024px){.right-sidebar{padding-top:var(--main-top-padding);padding-bottom:var(--main-top-padding)}}.sidebar header{z-index:1;transition:box-shadow .5s ease;display:flex;flex-direction:column;gap:var(--sidebar-element-separation)}@media(min-width:768px){.sidebar header{padding:0}}.sidebar header .site-avatar{position:relative;margin:0;width:var(--sidebar-avatar-size);height:var(--sidebar-avatar-size);flex-shrink:0}.sidebar header .site-avatar .site-logo{width:100%;height:100%;border-radius:100%;box-shadow:var(--shadow-l1)}.sidebar header .site-avatar .emoji{position:absolute;width:var(--emoji-size);height:var(--emoji-size);line-height:var(--emoji-size);border-radius:100%;bottom:0;right:0;text-align:center;font-size:var(--emoji-font-size);background-color:var(--card-background);box-shadow:var(--shadow-l2)}.sidebar header .site-meta{display:flex;flex-direction:column;gap:10px;justify-content:center}.sidebar header .site-name{color:var(--accent-color);margin:0;font-size:1.6rem}@media(min-width:1536px){.sidebar header .site-name{font-size:1.8rem}}.sidebar header .site-description{color:var(--body-text-color);font-weight:400;margin:0;font-size:1.4rem}@media(min-width:1536px){.sidebar header .site-description{font-size:1.6rem}}[data-scheme=dark] #dark-mode-toggle{color:var(--accent-color);font-weight:700}[data-scheme=dark] #dark-mode-toggle .icon-tabler-toggle-left{display:none}[data-scheme=dark] #dark-mode-toggle .icon-tabler-toggle-right{display:unset}#dark-mode-toggle{margin-top:auto;color:var(--body-text-color);display:flex;align-items:center;cursor:pointer;gap:var(--menu-icon-separation)}#dark-mode-toggle .icon-tabler-toggle-right{display:none}#i18n-switch{color:var(--body-text-color);display:inline-flex;align-content:center;gap:var(--menu-icon-separation)}#i18n-switch select{border:0;background-color:transparent;color:var(--body-text-color)}#i18n-switch select option{color:var(--card-text-color-main);background-color:var(--card-background)}html{font-size:62.5%;overflow-y:scroll}*{box-sizing:border-box}body{background:var(--body-background);margin:0;font-family:var(--base-font-family);font-size:1.6rem;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*{scrollbar-width:auto;scrollbar-color:var(--scrollbar-thumb)transparent}::-webkit-scrollbar{height:auto}::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb)}::-webkit-scrollbar-track{background-color:transparent}.article-page.hide-sidebar-sm .left-sidebar{display:none}@media(min-width:768px){.article-page.hide-sidebar-sm .left-sidebar{display:inherit}}.article-page .main-article{background:var(--card-background);border-radius:var(--card-border-radius);box-shadow:var(--shadow-l1);overflow:hidden}.article-page .main-article .article-header .article-image img{height:auto;width:100%;max-height:50vh;object-fit:cover}.article-page .main-article .article-header .article-details{padding:var(--card-padding);padding-bottom:0}.article-page .main-article .article-content{margin:var(--card-padding)0;color:var(--card-text-color-main)}.article-page .main-article .article-content .footnotes{font-family:var(--base-font-family)}.article-page .main-article .article-content img{max-width:100%;height:auto}.article-page .main-article .article-footer{margin:var(--card-padding);margin-top:0}.article-page .main-article .article-footer section:not(:first-child){margin-top:var(--card-padding)}.article-page .main-article .article-footer section{color:var(--card-text-color-tertiary);text-transform:uppercase;display:flex;align-items:center;font-size:1.4rem;gap:15px}.article-page .main-article .article-footer section svg{width:20px;height:20px;stroke-width:1.33}.article-page .main-article .article-footer .article-tags{text-transform:unset}.article-page .main-article .article-footer .article-copyright a,.article-page .main-article .article-footer .article-lastmod a{color:var(--body-text-color)}.article-page .main-article .article-footer .article-copyright a.link,.article-page .main-article .article-footer .article-lastmod a.link{box-shadow:unset}.widget--toc{background-color:var(--card-background);border-radius:var(--card-border-radius);box-shadow:var(--shadow-l1);display:flex;flex-direction:column;color:var(--card-text-color-main);overflow:hidden}.widget--toc ::-webkit-scrollbar-thumb{background-color:var(--card-separator-color)}.widget--toc #TableOfContents{overflow-x:auto;max-height:75vh}.widget--toc #TableOfContents ol,.widget--toc #TableOfContents ul{margin:0;padding:0}.widget--toc #TableOfContents ol{list-style-type:none;counter-reset:item}.widget--toc #TableOfContents ol li a:first-of-type::before{counter-increment:item;content:counters(item,".")". ";font-weight:700;margin-right:5px}.widget--toc #TableOfContents>ul{padding:0 1em}.widget--toc #TableOfContents li{margin:15px 0 15px 20px;padding:5px}.widget--toc #TableOfContents li>ol,.widget--toc #TableOfContents li>ul{margin-top:10px;padding-left:10px;margin-bottom:-5px}.widget--toc #TableOfContents li>ol>li:last-child,.widget--toc #TableOfContents li>ul>li:last-child{margin-bottom:0}.widget--toc #TableOfContents li.active-class>a{border-left:var(--heading-border-size)solid var(--accent-color);font-weight:700}.widget--toc #TableOfContents ul li.active-class>a{display:block}.widget--toc #TableOfContents>ul>li.active-class>a{margin-left:calc(-25px - 1em);padding-left:calc(25px + 1em - var(--heading-border-size))}.widget--toc #TableOfContents>ol>li.active-class>a{margin-left:calc(-9px - 1em);padding-left:calc(9px + 1em - var(--heading-border-size));display:block}.widget--toc #TableOfContents>ul>li>ul>li.active-class>a{margin-left:calc(-60px - 1em);padding-left:calc(60px + 1em - var(--heading-border-size))}.widget--toc #TableOfContents>ol>li>ol>li.active-class>a{margin-left:calc(-44px - 1em);padding-left:calc(44px + 1em - var(--heading-border-size));display:block}.widget--toc #TableOfContents>ul>li>ul>li>ul>li.active-class>a{margin-left:calc(-95px - 1em);padding-left:calc(95px + 1em - var(--heading-border-size))}.widget--toc #TableOfContents>ol>li>ol>li>ol>li.active-class>a{margin-left:calc(-79px - 1em);padding-left:calc(79px + 1em - var(--heading-border-size));display:block}.widget--toc #TableOfContents>ul>li>ul>li>ul>li>ul>li.active-class>a{margin-left:calc(-130px - 1em);padding-left:calc(130px + 1em - var(--heading-border-size))}.widget--toc #TableOfContents>ol>li>ol>li>ol>li>ol>li.active-class>a{margin-left:calc(-114px - 1em);padding-left:calc(114px + 1em - var(--heading-border-size));display:block}.widget--toc #TableOfContents>ul>li>ul>li>ul>li>ul>li>ul>li.active-class>a{margin-left:calc(-165px - 1em);padding-left:calc(165px + 1em - var(--heading-border-size))}.widget--toc #TableOfContents>ol>li>ol>li>ol>li>ol>li>ol>li.active-class>a{margin-left:calc(-149px - 1em);padding-left:calc(149px + 1em - var(--heading-border-size));display:block}.related-content{overflow-x:auto;padding-bottom:15px}.related-content>.flex{float:left}.related-content article{margin-right:15px;flex-shrink:0;overflow:hidden;width:250px;height:150px}.related-content article .article-title{font-size:1.8rem;margin:0}.related-content article.has-image .article-details{padding:20px;background:linear-gradient(0deg,rgba(0,0,0,.25) 0%,rgba(0,0,0,.75) 100%)}.article-content{font-family:var(--article-font-family);font-size:var(--article-font-size);padding:0 var(--card-padding);line-height:var(--article-line-height)}.article-content>p{margin:1.5em 0}.article-content h1,.article-content h2,.article-content h3,.article-content h4,.article-content h5,.article-content h6{margin-inline-start:calc((var(--card-padding)) * -1);padding-inline-start:calc(var(--card-padding) - var(--heading-border-size));border-inline-start:var(--heading-border-size)solid var(--accent-color);position:relative}.article-content h1 a.header-anchor,.article-content h2 a.header-anchor,.article-content h3 a.header-anchor,.article-content h4 a.header-anchor,.article-content h5 a.header-anchor,.article-content h6 a.header-anchor{transition:opacity .3s ease;opacity:0;position:absolute;left:0;width:var(--card-padding);text-align:center;color:var(--accent-color)}.article-content h1 a.header-anchor:before,.article-content h2 a.header-anchor:before,.article-content h3 a.header-anchor:before,.article-content h4 a.header-anchor:before,.article-content h5 a.header-anchor:before,.article-content h6 a.header-anchor:before{content:"#"}.article-content h1:hover a.header-anchor,.article-content h1:focus a.header-anchor,.article-content h2:hover a.header-anchor,.article-content h2:focus a.header-anchor,.article-content h3:hover a.header-anchor,.article-content h3:focus a.header-anchor,.article-content h4:hover a.header-anchor,.article-content h4:focus a.header-anchor,.article-content h5:hover a.header-anchor,.article-content h5:focus a.header-anchor,.article-content h6:hover a.header-anchor,.article-content h6:focus a.header-anchor{opacity:1}.article-content figure{text-align:center}.article-content figure figcaption{font-size:1.4rem;color:var(--card-text-color-secondary)}.article-content blockquote{position:relative;margin:1.5em 0;border-inline-start:var(--blockquote-border-size)solid var(--card-separator-color);padding:15px calc(var(--card-padding) - var(--blockquote-border-size));background-color:var(--blockquote-background-color)}.article-content blockquote .cite{display:block;text-align:right;font-size:.75em}.article-content blockquote .cite a{text-decoration:underline}.article-content hr{width:100px;margin:40px auto;background:var(--card-text-color-tertiary);height:2px;border:0;opacity:.55}.article-content code{color:var(--code-text-color);background-color:var(--code-background-color);padding:2px 4px;border-radius:var(--tag-border-radius);font-family:var(--code-font-family)}.article-content a,.article-content code{word-break:break-word}.article-content .gallery{position:relative;display:flex;flex-direction:row;justify-content:center;margin:1.5em 0;gap:10px}.article-content .gallery figure{margin:0}.article-content pre{overflow-x:auto;display:block;background-color:var(--pre-background-color);color:var(--pre-text-color);font-family:var(--code-font-family);line-height:1.428571429;word-break:break-all;padding:var(--card-padding)}[dir=rtl] .article-content pre{direction:ltr}.article-content pre code{color:unset;border:none;background:0 0;padding:0}.article-content .highlight{background-color:var(--pre-background-color);padding:var(--card-padding);position:relative}.article-content .highlight:hover .copyCodeButton{opacity:1}[dir=rtl] .article-content .highlight{direction:ltr}.article-content .highlight pre{margin:initial;padding:0;margin:0;width:auto}.article-content .copyCodeButton{position:absolute;top:calc(var(--card-padding));right:calc(var(--card-padding));background:var(--card-background);border:none;box-shadow:var(--shadow-l2);border-radius:var(--tag-border-radius);padding:8px 16px;color:var(--card-text-color-main);cursor:pointer;font-size:14px;opacity:0;transition:opacity .3s ease}.article-content .table-wrapper{padding:0 var(--card-padding);overflow-x:auto;display:block}.article-content table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:1.5em;font-size:.96em}.article-content th,.article-content td{text-align:left;padding:4px 8px 4px 10px;border:1px solid var(--table-border-color)}.article-content td{vertical-align:top}.article-content tr:nth-child(even){background-color:var(--tr-even-background-color)}.article-content .twitter-tweet{color:var(--card-text-color-main)}.article-content .video-wrapper{position:relative;width:100%;height:0;padding-bottom:56.25%;overflow:hidden}.article-content .video-wrapper>iframe,.article-content .video-wrapper>video{position:absolute;width:100%;height:100%;left:0;top:0;border:0}.article-content .gitlab-embed-snippets{margin:0!important}.article-content .gitlab-embed-snippets .file-holder.snippet-file-content{margin-block-end:0!important;margin-block-start:0!important;margin-left:calc((var(--card-padding)) * -1)!important;margin-right:calc((var(--card-padding)) * -1)!important;padding:0 var(--card-padding)!important}.article-content blockquote,.article-content figure,.article-content .highlight,.article-content pre,.article-content .gallery,.article-content .video-wrapper,.article-content .table-wrapper,.article-content .s_video_simple{margin-left:calc((var(--card-padding)) * -1);margin-right:calc((var(--card-padding)) * -1);width:calc(100% + var(--card-padding) * 2)}.article-content .katex-display>.katex{overflow-x:auto;overflow-y:hidden}.article-content kbd{border:1px solid var(--kbd-border-color);font-weight:700;font-size:.9em;line-height:1;padding:2px 4px;border-radius:4px;display:inline-block}.section-card{border-radius:var(--card-border-radius);background-color:var(--card-background);padding:var(--small-card-padding);box-shadow:var(--shadow-l1);display:flex;align-items:center;gap:20px;--separation:15px}.section-card .section-term{font-size:2.2rem;margin:0;color:var(--card-text-color-main)}.section-card .section-description{font-weight:400;color:var(--card-text-color-secondary);font-size:1.6rem;margin:0}.section-card .section-details{flex-grow:1;display:flex;flex-direction:column;gap:8px}.section-card .section-image img{width:60px;height:60px}.section-card .section-count{color:var(--card-text-color-tertiary);font-size:1.4rem;margin:0;font-weight:700;text-transform:uppercase}.subsection-list{overflow-x:auto}.subsection-list .article-list--tile{display:flex;padding-bottom:15px}.subsection-list .article-list--tile article{width:250px;height:150px;margin-right:20px;flex-shrink:0}.subsection-list .article-list--tile article .article-title{margin:0;font-size:1.8rem}.subsection-list .article-list--tile article .article-details{padding:20px}.not-found-card{background-color:var(--card-background);box-shadow:var(--shadow-l1);border-radius:var(--card-border-radius);padding:var(--card-padding)}.search-form{position:relative;--button-size:80px}.search-form.widget{--button-size:60px}.search-form.widget label{font-size:1.3rem;top:10px}.search-form.widget input{font-size:1.5rem;padding:30px 20px 15px}.search-form p{position:relative;margin:0}.search-form label{position:absolute;top:15px;inset-inline-start:20px;font-size:1.4rem;color:var(--card-text-color-tertiary)}.search-form input{padding:40px 20px 20px;border-radius:var(--card-border-radius);background-color:var(--card-background);box-shadow:var(--shadow-l1);color:var(--card-text-color-main);width:100%;border:0;-webkit-appearance:none;transition:box-shadow .3s ease;font-size:1.8rem}.search-form input:focus{outline:0;box-shadow:var(--shadow-l2)}.search-form button{position:absolute;inset-inline-end:0;top:0;height:100%;width:var(--button-size);cursor:pointer;background-color:transparent;border:0;padding:0 10px}.search-form button:focus{outline:0}.search-form button:focus svg{stroke-width:2;color:var(--accent-color)}.search-form button svg{color:var(--card-text-color-secondary);stroke-width:1.33;transition:all .3s ease;width:20px;height:20px}a{text-decoration:none;color:var(--accent-color)}a:hover{color:var(--accent-color-darker)}a.link{box-shadow:0 -2px rgba(var(--link-background-color),var(--link-background-opacity))inset;transition:all .3s ease}a.link:hover{box-shadow:0 calc(-1rem * var(--article-line-height))rgba(var(--link-background-color),var(--link-background-opacity-hover))inset}.section-title{text-transform:uppercase;margin-top:0;margin-bottom:10px;display:block;font-size:1.6rem;font-weight:700;color:var(--body-text-color)}.section-title a{color:var(--body-text-color)}.danger,.note,.warning{border-left-width:.8rem!important}.warning{border-left-color:#fb0!important}.note{border-left-color:#4285f4!important}.danger{border-left-color:#e00!important} \ No newline at end of file diff --git a/search/index.html b/search/index.html new file mode 100644 index 0000000..7e5a99b --- /dev/null +++ b/search/index.html @@ -0,0 +1,16 @@ +Search +

+

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/search/index.json b/search/index.json new file mode 100644 index 0000000..f35c417 --- /dev/null +++ b/search/index.json @@ -0,0 +1 @@ +[{"content":"A big part of my research has been around vocabularies and semantic annotation. And, to be honest, I\u0026rsquo;ve grown increasingly dissatisfied with the field. To the point where I dread having to work on it. Some day I will write about it in length, but today I\u0026rsquo;ve stumbled upon a post that covers the topic quite well: The Semantic Web is Dead - Long Live the Semantic Web (styling mine).\nIn particular, this section has really resonated with me:\nAcademics and Industry The political economy of academia and its interaction with industry is the origin of our current lack of a functional Semantic Web.\nAcademia is structured in a way that there is very little incentive for anyone to build usable software. Instead, you are elevated for rapidly throwing together an idea, a tiny proof of concept, and to iterate on microscopic variations of this thing to produce as many papers as possible.\nIn engineering, the devil is in the detail. You really need to get into the weeds before you can know what the right thing to do is. This is simultaneously a devastating situation for industry and academia. Nobody is going to wait around for a team of engineers to finish building a system to write about it in Academia. You’ll be passed immediately by legions of paper pushers. And in industry, you can’t just be mucking about with a system that you might have to throw away.\nTags: [semantic web]","date":"2025-03-07T10:24:52+01:00","permalink":"https://balkian.com/p/rdf-is-dead/","title":"RDF Is Dead"},{"content":"Background TL;DR I work in academia. This post focuses on advice I\u0026rsquo;d give a younger me to be a more effective supervisor and project lead.\nMy role in my research group has evolved from individual contributor to project lead that manages a team of multiple students. This often involves coordinatating with other senior researchers and their teams.\nThis post is a collection of advice I would have given myself back when I started this journey. It is also an excuse to reflect on these ideas I\u0026rsquo;ve been implicitly applying everyday, and maybe learn a few things more in the process.\nIn my field, projects are often tied to a specific grant or some sort of public funding. This means that the main concern of the lead is to ensure that the results at the end of the project match the description in the project proposal. It also typically means maximizing the number of publications related to the project and their overall impact.\nTo do so, most projects rely on three types of staff: a) senior researchers (post-doc); b) junior researchers (PhD students); and c) interns doing their bachelor\u0026rsquo;s or master\u0026rsquo;s thesis. The level of contribution is generally inversely propotional to the level of mastery of the contributor: PhD students design and develop the main contributions (both software and experimental) under the supervision of senior researchers (advisor/supervisor), and interns take care of tasks that are narrow in scope and not crucial to any academic contribution. For instance, a PhD student may develop a new model for text classification, and an intern will wrap that model in an HTTP service with a nice UI. When the service and UI part is intricate and has some potential academic merit, that task may be conducted by a PhD student as part of their thesis. That was precisely the case with Senpy, which was part of my PhD thesis, and it has since been used by dozens of students to develop services in the context of other research projects.\nReasons to form a team In my opinion, a team has two advantages over a single contributor. The first one is that collaboration often generates synergies, leading to surprising and enriching results (a team is greater than the sum of its parts) Carefully selecting your team members and creating an environment that is conducing to these synergies is a topic on its own, and I will not get too deep into it here.\nThe second advantage can be summarized as concurrency: tasks can be tackled by more than one member. This often implies some sort of parallelism. Tasks tend to be split between different members, in hopes of speeding up the process. But it can also be beneficial to have concurrency even in the absence of parallelism: different members can take turns solving the same problem. This is common when the task requires exclusive access to a resource (e.g., performing an experiment on an expensive machine).\nChallenges of teams (of students) Just like in computer science, coordination in a concurrent project involves a non-negligible overhead. The objective is to minimize this overhead.\nI\u0026rsquo;ve really struggled with managing teams in the past. Given my context, I often attributed the failures to lack of time (having to juggle teaching and research), lack of training and preparation on the intern\u0026rsquo;s side (they\u0026rsquo;re often undergrads), lack of appeal or definition of the task (too academic-y), or any other external factors.\nWhile all those aspects play an important role, some of them are out of our control as a project lead in academia: grants require a certain type of project and workflow, and the quality of our interns is bounded by the quality of our degrees. So I think it is more constructive to focus on things that we can control. In other words: we have to play the hand we\u0026rsquo;re given.\nBesides, there is no merit in achieving good results with excellent students/engineers. They would succeed on their own even if you weren\u0026rsquo;t there. The real test for a good leader is succeeding with a subpar team.\nIn that vein, I\u0026rsquo;ve reflected on my mistakes as a leader, and the inefficiencies of the teams around me. I\u0026rsquo;d classify my failures in the following areas:\nDelegation (and lack thereof). Piling up too many tasks and blocking progress. Communication. Not having a coherent view of the state of affairs, the details of specific tasks, the general processes to follow, or the priorities of different tasks within a project. Direction (or purpose). Not having a common direction Delegation I have a tendency to become a bottleneck in any project I am involved in: many tasks end up depending on me, either directly or indirectly. In the concurrency metaphor, I become a lock for many tasks, and a single executor for the rest. This issue was not that apparent early on, when most of my work was as an individual contributor or I had the bandwidth to supervise and complete my tasks.\nI think it is quite common to feel like delegating a task in these scenarios means:\nDefining the task in advance Choosing an assignee for the task Setting a deadline for the task Explaining the task and the relevant context Replying to multiple questions by e-mail or in person. Extra points when the questions make you wonder if any part of the explanation was ever clear. Reviewing the results after the deadline Realizing the assignee misunderstood the task or delivered something not even close to what you agreed upon Going back to point 3. When you\u0026rsquo;re unlucky or short on time: giving up and doing the task yourself Many times, if felt like delegating tasks only lead to frustration and wasted time. Especially when compared to the alternative:\nDefining the task Setting a deadline Finishing the task Profit Luckily, some students and projects were an exception to this. They worked autonomously and delivered something beyond the minimum requirements. This reinforced my helplessness and the feeling that the problem was not being able to work with experienced engineers.\nHowever, I now believe that the truth lies somewhere in between. Sometimes your circumstances make it quite hard or inefficient to delegate tasks. And some times may not be good candidates for delegation. But most of the times you can take advantage of having an extra pair of hands, you just have to do that effectively.\nCommunication Small teams rely on implicit knowledge more than they realize. Even more so if the team is made up of highly specialized people that have worked in the same environment with mostly the same people for years.\nCommunication is a broad term. It includes technical and concrete things such as how a certain task should be done. But it also includes broader things like etiquette, organizational values, and who is more willing to help you out on certain topics on a Friday afternoon.\nHere, I would take a page out of Python\u0026rsquo;s zen and recommend that \u0026ldquo;explicit is better than implicit\u0026rdquo;. Implicit (or tacit) knowledge comes with a whole set of drawbacks:\nIt makes onboarding new users harder. Without a common knowledge base, all knowledge transference has to rely on personal interactions. Even worse, those interactions are probably organized on the spot, and are likely to miss important points. It makes you heavily reliant on your current members (and their memory). It impedes proper evaluationn and progress, since they are not written anywhere. It increases the likelihood of misunderstandings when two members have conflicting beliefs, and makes it harder to detect them until it is too late. It makes contradictions and (unknowingly) changing your mind much more likely. It can happen to the best of us, especially if you are involved in too many projects. When contraditions happen often, your colleagues will learn not to rely on your opinion. On the other hand, communication has to go both ways. This means that your newer members need to be able to communicate when something is going wrong or can be improved (backpressure). They should also feel free to talk about their motivation, state of mind, and feelings, when appropriate. That last part is quite subjective, of course. Try to find your - and your organization\u0026rsquo;s - middle ground between \u0026ldquo;I don\u0026rsquo;t care how you feel, just do your job\u0026rdquo; and \u0026ldquo;sure, you can go to the Maldives on short notice. Oh, and don\u0026rsquo;t worry about not having met a deadline in months, I\u0026rsquo;m sure you\u0026rsquo;re stressed and can use some vacation but will work remotely if we need you\u0026rdquo;.\nI personally feel a sweet spot is treating your coworkers like people, being empathetic and compassionate. Part of being a good coworker is fulfilling the duties and obligations you accepted when signing your contract. First and foremost, because not fulfilling them means someone else will have to work harder to make up for it. And, secondly, because doing our part is the only way to move the organization (and research) forward.\nDirection By failure in direction I mean not keeping a consistent and shared set of general goals, principles and values in your organization. In order to really take part in any enterprise, you need to have a clear understanding of the objectives and motivation. When it comes to specific tasks, what you\u0026rsquo;re doing is often not as important as the why you\u0026rsquo;re doing it. In fact, there may be times where you aren\u0026rsquo;t truly sure of what exactly it is that you are doing, but you trust the process and the motivation behind the task.\nI\u0026rsquo;ve seen two failure modes in this regard. The first one is to not have a clear direction. The end result is that members of the team are not really that committed. If no other why is provided, we are only left with because they pay me to do it. And academia is not known to pay particularly well, to be honest.\nThe other mode is to provide contradicting or incompatible directions. This can be in a short period of time, leading to the impression that there isn\u0026rsquo;t really any conviction in the message. But it can also be done over a longer period of time. That can be perfectly acceptable, provided that the change in direction is justified and compatible with the principles of the organization.\nFailure in direction is somewhat related to communication, but it is subtly different. An organization can excel at communication, but change their direction constantly. Arguably, a thorough communication strategy makes radical changes in direction less likely. On the one hand, a change in direction needs to be documented, which can be a pain. On the other hand, a written change is easier to spot and more likely to generate complaints.\nRules I\u0026rsquo;d argue that the path to successfully managing a research team lies in roughly the following key goals:\nFostering autonomy Avoiding miscommunication Optimizing your contribution The remaining of the post will be a series of tasks or rules to achieve these goals.\nMost of these ideas probably generalize well to collaboration outside of academia, but I hesitate to make more general claims.\nFostering automony The tips here are aimed at avoiding supervision overhead and training future leads.\nProvide a (simplified version of the) bigger picture Try to paint the bigger picture, even for menial tasks within large projects. For you, this may be the nth project you\u0026rsquo;re involved in this year, but the new intern may not have even heard about European projects before. Going back to the idea of direction, it is easier to work on something if you know the context of your work.\nHaving a general idea of the project and the context of your task will also help you make decisions on your own. For instance, if I am told to develop a shiny new API for text classification, I may have to ask many questions: 1) what will be input look like?; 2) what should the parameters be?; 3) am I using POST or GET requests?; 4) should I return a JSON object or an XML?\u0026hellip; What if, instead of that, I am also told this API will be used in the context of project X, that our organization will be the only consumers of the API, and they also give me a link to the project\u0026rsquo;s docs. I may be able to figure out some of those answers on my own (e.g., by finding examples in the project\u0026rsquo;s website), or decide that some questions are not vital at this point (e.g., if we are the only consumers, we can change from GET to POST if we need to much more easily).\nOne caveat here is that a link to the documentation or some vague words about the project do not constitute proper context. You are responsible for summarizing the important bits of the context, providing instructions on how to navigate the reference materials, and being open to answer questions that may arise in the exchange.\nDo not discuss implementation details unless strictly necessary There is a fine line between discussing a non-trivial implementation detail and bikeshedding for hours about class names and code best practices. For that reason, you should try to prioritize discussing high-level parts of the problem and the assignment, and trust the student to figure out the details on their own, or come back to you for clarification.\nIt is very common that students focus on very specific details when they are sharing their progress with their supervisors. They will generally try to start by showing snippets of code and their results. I find it helpful to remind them to explain their problems top-to-bottom, starting with a sentence or two about the context of their project, the description and motivation of the specific task they were doing, and the relationship with previous (and future) tasks. That usually helps figure out the level of understanding of the student, whether there are any conceptual errors, and whether the specific block or problem is really worth discussing during the meeting.\nSome technical problems will warrant a discussion in detail, either due to their complexity or their importance to the project. In those cases, always limit the time you will spend on that specific issue ahead of time, and make sure to allow for some time at the end of the meeting to go back to any important high-level details.\nIf there are other students that worked on similar projects, do not hesitate to refer your new student to them. It can be an opportunity for them to collaborate, and for the original student to work on explaining and teaching technical issues.\nProvide feedback Make a point of evaluating the results of each student on every level, and provide constructive and actionable feedback to them. Even if no technical issues arise during the project, try to review the code and give some tips (e.g., formatting, code structure, DRY). Try to focus on bigger issues and enforcing best practices before nitpicking and giving feedback on small subjective improvements.\nMake it clear when your feedback is objective/best practice (e.g., a function is deprecated) and when it is a matter of preference. If it is the latter, try to provide more than one alternative, to encourage them to think about it and make an educated decision.\nTake documentation and knowledge transfer seriously Taking the time to write down basic documentation can save a lot of time in the long run. Besides, most of the job of mentoring a new student is lost when that student finishes their degree and leaves to find a job in industry. Good documentation can remain in your organization and be extended long after the intern is gone.\nThis is very obvious for specific tools, whether internal or public. Good documentation means any new member can check the tool and use it without much assistance. Even better documentation helps newcomers contribute to the tool. Make sure to make it clear who to approach if something is missing from the documentation, and make it easier to do so than to make assumptions and use the tool incorrectly.\nWriting documentation can be very time consuming, and sometimes it is hard to know exactly what things to focus on when writing the docs. You need to anticipate the needs of the future user. If you are short on time, a good strategy is to delegate the writing of the documentation. Instead of going into details, you can write a very barebones version and training a new user to use and contribute to your tool. Then, leave it up to the new user to extend the documentation, including more details and pitfalls. As a bonus, reading and fixing the docs will give you a better sense of how well that new user understands the tool, as well as possible improvements.\nThis tip also applies to more general areas such as machine learning, graph neural networks, or simulation. Just remember you do not need to reinvent the wheel in those cases. A simple summary and a list of references to expand on the topic could be more than enough. Make sure to also include any specifics that apply to your organization. For instance, point to repositories on github (public or private) that can be used to explore the topic, examples of similar projects in the domain, etc.\nIdentify what information is important for any new hire and present it to them as clearly as possible. Part of that information should be where and how common knowledge is stored and shared, should they need more information in the future. Make this documentation as easy to discover and consume as possible. Centralizing this common information in the form of a wiki is often a good idea.\nLastly, make it easy for any member of your organization to update this common documentation, and encourage them to do so. Whenever a member asks you something useful that is not documented, don\u0026rsquo;t just answer the question. Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer. If your organization\u0026rsquo;s culture does not encourage using these docs, they will quickly get outdated and fall out of use.\nOne example of taking this documentation approach really seriously is Oxide (computer company). They have a process they call Request For Discussion (RFD), which they use to discuss and document both technical and organizational decisions. For instance, they have RFDs on why they record every meeting, RFDs about their choice of database, and even an meta-RFD that discusses the motivation RFDs and how the process should work.\nTrust your teammate\u0026rsquo;s ability to learn I\u0026rsquo;ve been bitten by this way too many times. Your students are probably more capable of learning than you think, especially if you have set up your documentation right. What they lack in experience, they make up for with free time, a (more) neuroplasticity and determination.\nSure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.\nUse tools wisely Your students probably have little experience with code versioning, reviewing processes, time management, etc. A good choice of tools and some training can go a long way and make your life much easier in the long run. It will also give your students a taste of what working in a bigger/real company feels like and a head start.\nFor instance, using git makes it easier to collaborate on code. It also ensures that your results will not be lost if your student\u0026rsquo;s laptop gets stolen.\nUsing GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students. It will force them to commit working code, and it will make it easier to check their results and discuss the end result.\nUsing overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results. You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration. In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.\nAlso, on a related note, make sure every team member has a proper development setup. It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work. It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.\nEncourage cooperation Do not become the center of every conversation. If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.\nThe ability to discuss with your peers and report only when needed will be extremely important for them in the future. They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are). That might lead to valuable insights and improvements for your team and project.\nMoreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.\nReward proactivity The whole point of this section is to get your team to work independently when possible. Be explicit about this goal to make sure it is clear to everyone. And encourage behavior that aligns with this goal, even on a small scale.\nFor instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements. Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time. Do not jump straight to criticize it. Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.\nDon\u0026rsquo;t be a perfectionist Perfect is the enemy of done. It is also the enemy of a happy co-worker.\nTry to remember that you are dealing with students, and you were probably no better at their age. Besides, you probably delegated the task beause you did not have any spare time to do it yourself. FIXME is often better than TODO.\nTake the opportunity to provide some feedback and teach them something useful. Some mistakes are also worth adding to your documentation, or presenting to other students in a presentation.\nAvoiding miscommunication A common source of wasted effort and unnecessary back-and-forth is miscommunication. These are some tips to help keep everyone on the team informed and aligned.\nMake priorities clear All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks. This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.\nDefine boundaries (and abstractions) Once again, the goal is generally to achieve some sort of parallelism between your team members. In order to do that, they need to know how they will interact with each other.\nOn a more general level, this means knowing the responsibilities and scope of your work.\nOn a more specific level, it means knowing their dependency graph. In other words, whether the progress of one team member will depend on the results of another one. Whenever there is a dependency, the interface should be made very clear. This often takes the form of an API, a file with a given format, or a section of a document.\nTake some time to define the boundary as precisely as needed at that point in the project. I would suggest having specific examples that you can discuss and modify. It is hard to discuss in the abstract, especially for inexperienced contributors. When in doubt, default to the simplest option (e.g., a common file vs using a database). Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there. Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.\nOne type of failure I\u0026rsquo;ve seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples. That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.\nBe approachable Did I wrote a whole section about autonomy? Yes. Is the end goal to do more and talk less? Also yes. Thing is, no process is perfect, and misunderstandings are bound to happen at some point. If your only response to questions is a grumpy face or a \u0026ldquo;read the freaking docs\u0026rdquo;, your students will not alert you when something really needs your attention, and you will find out too late. For instance, the documentation may be unclear, or your processes may be inadvertedly alienating new members and making new hires harder.\nAnother way to be approachable is to be clear about your shortcomings, and whether something you are saying is negotiable and/or debatable. My rule of thumb is to err on the side of negotiable, and only be strict when it is really necessary (e.g., time constraints or an unproductive student). We are all more likely to finish our tasks if we feel them ours, if we a say in how and when to perform them.\nJust to be clear, approachable does not mean you have to be their confident or their best friend. It also does not mean that it is okay to challenge or question you continuously. Some times it is okay to simply say \u0026ldquo;just do as I say\u0026rdquo;.\nReview frequently One type of review is individual. It involves reviewing code on github, or reading deliverables and papers on overleaf. It can help catch misunderstandings, and measure the true rate of progress in the individual tasks. The other type of review is done as a group, by going through the key progress and action points. This type of review helps everyone stay on the same page, and catch any general drifts in the project, such as misaligned priorities.\nThe frequency of each type of review depends on the specific nature of the project, the types of tasks being performed by the student, and your confidence on the student\u0026rsquo;s abilities.\nOptimizing your contribution Tips on optimizing your contribution to the team.\nPrioritize, prioritize, prioritize Part of your job as a project lead is to identify the main goals in a project and to prioritize the tasks that will lead you there. On the other hand, you are part of a research group, and you should be actively involved in its health and future. Lastly, you are also in charge of the life-long project that is your research career.\nIn all these cases, your goal should be to identify the long term goals, come up with a sound strategy, and prioritize the tasks that will lead you and your group there. Keeping your priorities straight will help you make steady progress, and avoid bikeshedding and changing goalposts. It will also help you steer your progress in the right direction, since we all have limited time and effort and can\u0026rsquo;t do everything at once.\nThe fact that your time is limited also means that you will need to decide how to prioritize these three roles. I\u0026rsquo;ve listed them in increasing level of importance for me. It means that it is okay to focus on a specific project for a while, but if progress in your career is stalled - usually through publication - you need to reevaluate and concentrate your efforts on that.\nBe okay with (short-term) inefficiencies I\u0026rsquo;ve personally struggled with delegating tasks that will take me orders of magnitude less work than they will a student. Thing is, most tasks will fall under this category, and your time is limited, so you have to delegate if you want to have time for more important matters. If you never delegate any tasks, you are not allowing your team to learn and catch up on whatever technical skills are required. Besides, you are not improving yourself on the managerial side of things. It turns out delegating is hard, it requires a whole set of non-technical skills. I suspect this is oftentimes the reason we don\u0026rsquo;t do delegate in the first place: delegating is hard, and technical tasks are usually more straightforward, so we just don\u0026rsquo;t want to do the work.\nDon\u0026rsquo;t neglect training You are a senior researcher. You probably know how to solve problems in your domain quite efficiently. In my case, that means processing data and developing code.\nThat means I could dedicate my days to processing data and developing new code for my group. That group would likely be used in multiple projects. However, there is a hard limit to how much code I can push out in a day, especially if you take into account other obligations such as teaching.\nA wiser strategy would be to set aside some of that coding time to instead help students become better programmers. Firstly, because those students will be thankful and more motivated to work than when they are left to learn on their own without much guidance. Secondly, because those students will then be more prepared to help me out if I delegate a task to them. And, lastly, because these students have a whole life in fron of them. A life full of big projects of their own, and contributions to society. That little training time can have a compounding effect in the future.\nSet a time limit for your interactions in advance Really long slots can easily lead to bikeshedding and going unnecessarily deep into implementation details, which is clearly an inefficient use of your time. Even worse, our attention span and memory are finite, so longer and dense meetings can lead to fatigue and to missing or dilluting important points in the conversation.\nFor these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary. You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.\nBeyond your team The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control. But teams rarely work in isolation, you will most likely In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization\u0026rsquo;s culture and sense of belonging.\nMany of the aspects I talked about in the team section apply here. For instance, the obsession with documentation can - and should - be applied organization-wise. The same goes for defining boundaries and using concrete examples when collaborating with other teams. For most intents and purposes, you can treat other teams as another contributor to your team. Just one that will be more costly and slow to interact with.\nIf possible, I\u0026rsquo;d try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved. Avoid involving whole teams in discussions when the broad strokes have not been defined yet. The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.\nOn the organization\u0026rsquo;s side, I would suggest having an honest conversation about your core principles. I really liked Bryan Cantrill\u0026rsquo;s talk about principles of technology leadership. He goes deep into the effects that principles have had on well known companies, and how to go about defining your company\u0026rsquo;s principles. I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.\nMore generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive. And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier. Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.\nTags: [team management]","date":"2025-03-05T09:25:54+01:00","permalink":"https://balkian.com/p/efficient-collaboration/","title":"Tips for efficient collaboration"},{"content":"In the RDF world, data is expressed as a collection of triples. These triples can contain IRIs that may or may not be accessible or valid. And the use of these IRIs may or may not adhere to a vocabulary. Checking the validity of the IRIs and the semantics of the triples is an additional step.\nThe rdflib way rdflib only models IRIs, values and namespaces. Developers need to be cognisant of the URIs they are using, and the vocabularies being used. Prior to version 2.0, senpy followed a very similar model. It had a base class to represent a generic node. Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. Here is an example of a subclass, Entity.\n1 2 3 4 5 entry = Entry() entry[\u0026#39;vocab:property\u0026#39;] = 25 print(entry.jsonld()) Would print something like this:\n1 2 3 4 5 { \u0026#34;@id\u0026#34;: \u0026#34;:Entry_202505....\u0026#34;, \u0026#34;@type\u0026#34;: \u0026#34;prefix:Entity\u0026#34;, \u0026#34;vocab:property\u0026#34;: 25 } Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. Moreover, there is way to enforce that the vocabularies and the\nPros:\nFlexible/extensible Lightweight. This is mostly JSON-LD in Python\u0026rsquo;s clothing. Naturally maps to both rdflib and writing json-ld Cons:\nDiscoverability. Documentation and examples are needed to know which attributes to use Error-prone. It is easy to misuse a property, or introduce typos Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used. The object-oriented way An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically validate objects at runtime. There are tools like pydantic that make this process very simple. Then, we only need to define how your models should be serialized into JSON-LD. We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.\nThis model works great when all the possible attributes are known ahead of time. But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut es. This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. It also breaks if different parts of the code need to add their own attributes on the same data at the same time. This was precisely the case of senpy, where entities are annotated by different plugins, each providing a different set of annotations.\nPros:\nDiscoverability. All possible attributes are known ahead of time, including their possible types. Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass. Cons:\nRigidity. Adding new types of annotations requires modifying the models, in the main module. Polymorphism. A hybrid approach Whichever solution is chosen in the end, it needs to:\nMake it easy and error-proof to add the most common types of annotations Allow for additional annotations/attributes to be added Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones Allow for deserialization of custom types Allow multiple consumers to add their own annotations Tags: [rdf json-ld pydantic python]","date":"2025-02-26T23:22:59+01:00","permalink":"https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/","title":"Bridging RDF, JSON-LD and Dataclasses"},{"content":"Long story short: I\u0026rsquo;m now using uv, and so should you. It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.\nContext For years, my strategy to manage python projects has been a mix of a custom setup.py, several hand-crafted requirements.txt files (through pip freeze), a custom virtualenv per project, and multiple tools to upload to PyPI. Although this works, this setup has many drawbacks:\nIt requires user intervention (creating a venv, sourcing it, handling new deps). This isn\u0026rsquo;t ideal if you want new (probably inexperienced) users to use your projects. On a similar note, the whole process needs to be well documented if you want other users to contribute or maintain the code. Pinning dependency versions is finicky, and I\u0026rsquo;ve run into problems beause of that. Creating a new project involves a template, or copying files from an older project. Of course, this is nothing new. There is a whole site dedicated to packaging your Python project. A plethora of different projects have come and go, with varying degrees of success.\nAlternatives (poetry) About a year before trying uv, I tried to catch up with the ecosystem and get to know the blessed new way. However, the task proved to be a little more difficult, as the landscape is filled with a myriad of alternatives, each with their own set of drawbacks and detractors. Packaging has historically been a weak spot, in ironical contradiction to the Zen of Python\u0026rsquo;s \u0026ldquo;There should be one\u0026ndash; and preferably only one \u0026ndash;obvious way to do it\u0026rdquo;,\nI eventually settled on poetry. Mostly because it seemed like the most popular alternative.\nThere are many things I liked about it. First of all, having a convention for dependencies (pyproject.toml) and a tool that properly handles them was nice. It also removed the need to remember specific incantations to build and publish my Python projects. Lastly, I mixed it poetry2nix to create reproducible python environments using nix. This makes for a very powerful experience.\nHowever, there were multiple hiccups. First of all, it took me some time to figure out which specific fields to use (each tool can define ad-hoc properties in a the pyproject.toml file), and some of them seemed redundant with the more generic ones. Full disclosure, this specific point might be a mistake on my side, and I do not remember the details. The second one is speed. (Re-)creating an environment took a non-negligible amount of time.\nEnter light uv According to its repository, uv can replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. Not only that, but it also claims to do that 10-100 times faster than pip. I must admit that it being written in rust was a another selling point for me, as I\u0026rsquo;m looking for excuses to collaborate in a decently-sized rust projejct.\nInstalling it is dead simple: simply download the binary (e.g., with curl) or run pip install uv. You won\u0026rsquo;t need much more: uv seems to just do the right thing out of the box. And it does it really, really fast. The rest of the time it gets out of the way.\nMy only gripe so far is that I don\u0026rsquo;t seem to find a built-in command to drop into a shell, but that is nothing that uv run $SHELL cannot fix.\nCommon operations Initialize a repository 1 uv init Adding dependencies 1 uv add senpy Running commands inside the environment 1 2 3 4 uv run \u0026lt;COMMAND\u0026gt; # e.g., run a shell using your python version and dependencies uv run $SHELL Dependency tree 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 uv shell Resolved 44 packages in 1ms my-project v0.1.0 ├── fastapi[standard] v0.115.8 │ ├── pydantic v2.10.6 │ │ ├── annotated-types v0.7.0 │ │ ├── pydantic-core v2.27.2 │ │ │ └── typing-extensions v4.12.2 │ │ └── typing-extensions v4.12.2 │ ├── starlette v0.45.3 │ │ └── anyio v4.8.0 │ │ ├── exceptiongroup v1.2.2 │ │ ├── idna v3.10 │ │ ├── sniffio v1.3.1 │ │ └── typing-extensions v4.12.2 │ ├── typing-extensions v4.12.2 │ ├── email-validator v2.2.0 (extra: standard) │ │ ├── dnspython v2.7.0 ... Tags: [python]","date":"2025-02-17T23:02:47+01:00","image":"https://balkian.com/img/uv.png","permalink":"https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/","title":"uv - One rust tool to rule all pythons"},{"content":"This is a quick and easy recipe to add a default.nix to any Python project with a requirements.txt file:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 with import \u0026lt;nixpkgs\u0026gt; { }; let pythonPackages = python311Packages; in pkgs.mkShell rec { name = \u0026#34;impurePythonEnv\u0026#34;; venvDir = \u0026#34;./.venv\u0026#34;; buildInputs = [ # A python interpreter including the \u0026#39;venv\u0026#39; module is required to bootstrap # the environment. pythonPackages.python # This execute some shell code to initialize a venv in $venvDir before # dropping into the shell pythonPackages.venvShellHook # Those are dependencies that we would like to use from nixpkgs, which will # add them to PYTHONPATH and thus make them accessible from within the venv. pythonPackages.numpy pythonPackages.requests # In this particular example, in order to compile any binary extensions they may # require, the python modules listed in the hypothetical requirements.txt need # the following packages to be installed locally: taglib openssl git libxml2 libxslt libzip zlib ]; # Now we can execute any commands within the virtual environment. # This is optional and can be left out to run pip manually. postShellHook = \u0026#39;\u0026#39; pip install -r requirements.txt \u0026#39;\u0026#39;; } Now, you will get a clean environment by running:\n1 nix-shell Tags: [nix python]","date":"2023-11-13T18:21:46+01:00","permalink":"https://balkian.com/p/nix-recipe-for-python-projects/","title":"Nix Recipe for Python Projects"},{"content":"Kanata is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. On the other hand, you need to configure them on every PC/OS you\u0026rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.\nThe project was inspired by the more popular KMonad, and the author cites some of the differences. Both projects use a very similar configuration format based on lisp. The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. Here\u0026rsquo;s a very complete config that serves as documentation.\nOne big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.\nKeyd is another alternative with a more declarative configuration format, which might lend itself to smaller.\nFor now I\u0026rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren\u0026rsquo;t enough keys for all the letters and symbols. In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. At least, that\u0026rsquo;s the idea. We\u0026rsquo;ll see if I like it enough to stick with it.\nFor now, here\u0026rsquo;s my very simple config:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 (defcfg ;; Your keyboard device will likely differ from this. linux-dev /dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse ;; Windows doesn\u0026#39;t need any input/output configuration entries; however, there ;; must still be a defcfg entry. You can keep the linux-dev entry or delete ;; it and leave it empty. ) (defsrc grv 1 2 3 4 5 6 7 8 9 0 - = bspc tab q w e r t y u i o p [ ] caps a s d f g h j k l ; \u0026#39; ret lsft \\ z x c v b n m , . / rsft lctl lmet lalt spc ralt rmet rctl ) (deflayer qwerty grv _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @warrows _ _ _ _ _ _ _ _ _ _ lctrl @alctrl @slsft @dlalt @flmet _ _ @jrmet @kralt @lrsft @;rctrl _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @smartspace _ _ _ ) (deflayer arrows _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @flmet _ left down up rght _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @smartspace _ _ _ ) (deflayer colemak grv XX XX XX XX XX XX XX XX XX XX XX XX _ tab q w f p b j l u y ; [ ] lctrl @alctrl @rlsft @slalt @tlmet g m @nrmet @eralt @irsft @orctrl \u0026#39; ret lsft XX z x c d v k h , . / rsft XX XX XX @smartspace XX XX XX ) (deflayer magic _ @clmk @qwerty _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ tab A-tab _ _ _ _ bspc esc _ ret _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ) (defalias warrows (tap-hold 200 200 w (layer-toggle arrows)) alctrl (tap-hold 200 200 a lctrl) slsft (tap-hold 200 200 s lsft) dlalt (tap-hold 200 200 d lalt) flmet (tap-hold 200 200 f lmet) jrmet (tap-hold 200 200 j rmet) kralt (tap-hold 200 200 k ralt) lrsft (tap-hold 200 200 l rsft) ;rctrl (tap-hold 200 200 ; rctrl) rlsft (tap-hold 200 200 r lsft) slalt (tap-hold 200 200 s lalt) tlmet (tap-hold 200 200 t lmet) nrmet (tap-hold 200 200 n rmet) eralt (tap-hold 200 200 e ralt) irsft (tap-hold 200 200 i rsft) orctrl (tap-hold 200 200 o rctrl) clmk (layer-switch colemak) qwerty (layer-switch qwerty) smartspace (tap-dance 200 ( (tap-hold 300 300 spc (layer-toggle magic)) (tap-hold 300 300 (one-shot 300 lalt) spc) a )) ) Tags: [linux logitech keyboard layout rust]","date":"2023-01-20T18:11:00Z","permalink":"https://balkian.com/p/kanata-advanced-keyboard-configuration/","title":"Kanata: advanced keyboard configuration"},{"content":"As a follow-up to my last post, I\u0026rsquo;ve decided to also configure my mk850 combo (k850 + m720 triathlon).\nSome notes:\nThe keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc) The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I\u0026rsquo;ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM. 1 2 3 4 5 6 7 evdev:input:* KEYBOARD_KEY_70039=leftctrl # bind capslock to w evdev:input:b0005v046DpB015* KEYBOARD_KEY_700e0=f19 KEYBOARD_KEY_700e2=unknown KEYBOARD_KEY_7002b=unknown After that, simply run:\n1 sudo udevadm hwdb --update \u0026amp;\u0026amp; sudo udevadm trigger Make sure the settings have been applied by running evemu-describe:\n1 sudo /sbin/evemu-describe /dev/input/event\u0026lt;id of your device\u0026gt; | grep KEY_ Tags: [linux logitech keyboard mouse]","date":"2021-10-30T00:00:01Z","permalink":"https://balkian.com/p/logitech-mb850-combi-in-linux/","title":"Logitech MB850 combi in linux"},{"content":"I recently got Logitech MX Keys for Mac keyboard at work. The German version, to be more precise. This version was three times cheaper than the Windows equivalent with either US or ES layout. Since I touch type anyway, I thought it was a bargain.\nAs soon as I plugged it in, I realized there were some glaring issues with the keyboard. First of all, the Meta/Super and Alt keys are reversed in this keyboard. In the normal/full version of this keyboard, Logitech gives an option to choose between Mac, Windows and iOS host, and that changes the behavior of the keys. In this version, tho, only iOS and Mac are available.\nBesides that, there\u0026rsquo;s the issue of the grave (tilde) and angle keys switched as well.\nSwitching these keys around would be very easy with Xorg, but Wayland once again complicates things\u0026hellip;\nThese issues almost made me return the keyboard. Luckily, tho, there is another option: configuring the keys one level lower than wayland (and X11), through hwdb.\nLong story short, this will configure any Logitech keyboard with the same product id (0x4092) to use a saner configuration:\n1 2 3 4 5 6 7 8 9 10 11 #File: /etc/udev/hwdb.d/90-logitech-keyboard.hwdb evdev:input:b0003v046Dp4092* KEYBOARD_KEY_700e2=leftmeta KEYBOARD_KEY_700e3=leftalt KEYBOARD_KEY_70039=leftctrl KEYBOARD_KEY_70064=102nd KEYBOARD_KEY_70035=grave KEYBOARD_KEY_700e7=rightalt KEYBOARD_KEY_700e6=rightmeta KEYBOARD_KEY_7006d=compose After that, simply run:\n1 sudo udevadm hwdb --update \u0026amp;\u0026amp; sudo udevadm trigger Tags: [linux logitech keyboard]","date":"2021-10-29T00:00:01Z","permalink":"https://balkian.com/p/logitech-mx-keys-for-mac-on-linux/","title":"Logitech MX Keys for Mac on Linux"},{"content":"Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.\nInstalling the kernel 1 2 git clone --depth 1 https://github.com/jakeday/linux-surface.git ~/linux-surface cp -a ~/linux-surface /media/\u0026lt;your usb\u0026gt; 1 2 3 cp -a /media/\u0026lt;your usb\u0026gt;/linux-surface ~/ cd ~/linux-surface/ sudo sh setup.sh Booting ubuntu first Switch out of Windows S mode.\nBoot into the \u0026ldquo;Command Prompt\u0026rdquo;.\nFrom Windows go to \u0026ldquo;change advanced startup options\u0026rdquo; and select \u0026ldquo;restart now\u0026rdquo;.\nWhen it reboots, choose the \u0026ldquo;Troubleshoot\u0026rdquo; option, then choose the \u0026ldquo;Advanced options\u0026rdquo; option, and finally choose the \u0026ldquo;Command Prompt\u0026rdquo; option.\nAfter the device reboots, login to the command prompt and then you should see a terminal with X:\\windows\\system32\u0026gt;\nAt the prompt, check your UEFI entries:\n1 bcdedit /enum firmware Copy UEFI entry of \u0026ldquo;Windows Boot Manager\u0026rdquo; to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d \u0026ldquo;Ubuntu\u0026rdquo;\nCopy the printed GUID number including the braces {} using Ctrl+C\nSet file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \\EFI\\ubuntu\\grubx64.efi\nSet Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.\n1 bcdedit /set {fwbootmgr} displayorder {guid} /addfirst Check your UEFI entries again: bcdedit /enum firmware You should see something like this:\n1 2 3 4 5 6 7 8 9 10 Firmware Boot Manager --------------------- identifier {fwbootmgr} displayorder {3510232e-f8eb-e811-95ce-9ecab3f9d1c4} {bootmgr} {2148799b-f8eb-e811-95ce-9ecab3f9d1c4} {312e8a67-c2f6-e811-95ce-3c1ab3f9d1de} {312e8a68-c2f6-e811-95ce-3c1ab3f9d1de} timeout 0 Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.\nTags: [linux surface go config]","date":"2019-06-01T00:00:01Z","permalink":"https://balkian.com/p/linux-on-the-microsoft-surface-go/","title":"Linux on the Microsoft Surface Go"},{"content":"This is a short tutorial on connecting a zigbee device (an Aqara cube) to an MQTT server, so you can control your zigbee devices from the network.\nIf you\u0026rsquo;re anything like me, you\u0026rsquo;re probably a sucker for IoT devices. For a long time, I\u0026rsquo;ve been using WiFi-enabled lights, and Amazon dash buttons to control them. To keep these (cheap Chinese) internet enabled devices away from your network and their respective cloud services, you\u0026rsquo;ll probably want to set up a dedicated network in your router (more on this on a future post, maybe). Another disadvantage of WiFi devices is that they\u0026rsquo;re relatively power hungry.\nA popular alternative is using ZigBee for communication. It is a dedicated protocol similar to bluetooth (BLE), with lower power requirements and bitrate.\nTake the (super cute) aqara cube as an example. It is a small cube that detects rotation on all of its axes, and tapping events. Here\u0026rsquo;s a video:\nTo connect to zigbee devices you will need a zigbee enabled gateway (a.k.a. hub), which connects to your WiFi network and your zigbee devices. Once again, this means adding an internet-enabled device to your home, and probably a couple of cloud services.\nAs an alternative, you can set up your own zigbee gateway, and control it to your home automation platform of choice (e.g. home assistant). We will cover how to set up a zigbee2mqtt gateway that is also connected to an MQTT server, so you can use MQTT to control your devices and get notifications.\nWhat you need:\nAqara cube. CC2531 zigbee sniffer. CC-debugger. You will need to flash your sniffer. For that, you only need to follow the instructions from the zigbee2mqtt documentation.\nOnce you\u0026rsquo;re done flashing, you\u0026rsquo;re ready to set up the zigbee2mqtt server. For convenience, I wrote a simple docker-compose to deploy a zigbee2mqtt server and a test mosquitto server:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 version: \u0026#39;2.1\u0026#39; services: zigbee2mqtt: image: koenkk/zigbee2mqtt container_name: zigbee2mqtt restart: always volumes: - ./z2m-data/:/app/data/ devices: - \u0026#34;/dev/ttyACM0\u0026#34; networks: - hass mqtt: image: eclipse-mosquitto ports: - 1883:1883 - 9001:9001 networks: - hass volumes: - ./mosquitto.conf:/mosquitto/config/mosquitto.conf networks: hass: driver: overlay You can test your installation with:\n1 2 3 4 5 6 ❯ mosquitto_sub -h localhost -p 1883 -t \u0026#39;zigbee2mqtt/#\u0026#39; online {\u0026#34;battery\u0026#34;:17,\u0026#34;voltage\u0026#34;:2925,\u0026#34;linkquality\u0026#34;:149,\u0026#34;action\u0026#34;:\u0026#34;rotate_right\u0026#34;,\u0026#34;angle\u0026#34;:12.8} {\u0026#34;battery\u0026#34;:17,\u0026#34;voltage\u0026#34;:2925,\u0026#34;linkquality\u0026#34;:141,\u0026#34;action\u0026#34;:\u0026#34;slide\u0026#34;,\u0026#34;side\u0026#34;:2} {\u0026#34;battery\u0026#34;:17,\u0026#34;voltage\u0026#34;:2925,\u0026#34;linkquality\u0026#34;:120} {\u0026#34;battery\u0026#34;:17,\u0026#34;voltage\u0026#34;:2925,\u0026#34;linkquality\u0026#34;:141,\u0026#34;action\u0026#34;:\u0026#34;wakeup\u0026#34;} zigbee2mqtt supports the following events for the aqara cube: shake, wakeup, fall, tap, slide, flip180, flip90, rotate_left and rotate_right. Every event has additional information, such as the sides involved, or the degrees turned.\nNow you are ready to set up home assistant support in zigbee2mqtt following this guide.\nTags: [mqtt iot zigbee]","date":"2019-01-06T10:00:00Z","permalink":"https://balkian.com/p/controlling-zigbee-devices-with-mqtt/","title":"Controlling Zigbee devices with MQTT"},{"content":"tqdm is a nice way to add progress bars in the command line or in a jupyter notebook.\n1 2 3 4 5 from tqdm import tqdm import time for i in tqdm(range(100)): time.sleep(1) Tags: [python]","date":"2016-09-28T18:47:00Z","permalink":"https://balkian.com/p/progress-bars-in-python/","title":"Progress bars in python"},{"content":"Today\u0026rsquo;s post is half a quick note, half public shaming. In other words, it is a reminder to be very careful with OAuth tokens and passwords.\nAs part of moving to emacs, I starting using the incredibly useful gh.el. When you first use it, the extension saves either your password or an OAuth token in your .gitconfig file. This is cool and convenient, unless you happen to be publishing your .gitconfig file in a public repo.\nSo, how can you still share your gitconfig without sharing your password/token with the rest of the world? Since Git 1.7.0, you can include other files in your gitconfig.\n1 2 [include] path = ~/.gitconfig_secret And now, in your .gitconfig_secret file, you just have to add this:\n1 2 3 [github] user = balkian token = \u0026#34;\u0026lt; Your secret token \u0026gt;\u0026#34; Tags: [github git dotfiles]","date":"2015-04-10T17:47:00Z","permalink":"https://balkian.com/p/sharing-dotfiles/","title":"Sharing dotfiles"},{"content":"Zotero is an Open Source tool that lets you organise your bibliography, syncing it with the cloud. Unlike other alternatives such as Mendeley, Zotero can upload the attachments and data to a private cloud via WebDav.\nIf you use nginx as your web server, know that even though it provides partial support for webdav, Zotero needs more than that. Hence, you will need another webdav server, and optionally let nginx proxy to it. This short post provides the basics to get that set-up working under Debian/Ubuntu.\nSetting up Apache First we need to install Apache:\n1 sudo apt-get install apache2 Change the head of \u0026ldquo;/etc/apache2/sites-enabled/000-default\u0026rdquo; to:\n1 \u0026lt;VirtualHost *:880\u0026gt; Then, create a file /etc/apache2/sites-available/webdav:\n1 2 3 4 5 6 7 8 9 10 11 12 13 Alias /dav /home/webdav/dav \u0026lt;Location /dav\u0026gt; Dav on Order Allow,Deny Allow from all Dav On Options +Indexes AuthType Basic AuthName DAV AuthBasicProvider file AuthUserFile /home/webdav/.htpasswd Require valid-user \u0026lt;/Location\u0026gt; Ideally, you want your webdav folders to be private, adding authentication to them. So you need to create the webdav and zotero users and add the passwords to an htpasswd file. Even though you could use a single user, since you will be configuring several clients with your credentials I encourage you to create the zotero user as well. This way you can always change the password for zotero without affecting any other application using webdav.\n1 2 3 4 sudo adduser webdav sudo htpasswd -c /home/webdav/.htpasswd webdav sudo htpasswd /home/webdav/.htpasswd zotero sudo mkdir -p /home/webdav/dav/zotero Enable the site and restart apache:\n1 2 3 4 sudo a2enmod webdav sudo a2enmod dav_fs sudo a2ensite webdav sudo service apache2 restart At this point everything should be working at http://\u0026lt;your_host\u0026gt;:880/dav/zotero\nSetting up NGINX After the Apache side is working, we can use nginx as a proxy to get cleaner URIs. In your desired site/location, add this:\n1 2 3 4 5 6 7 location /dav { client_max_body_size 20M; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $host; proxy_pass http://127.0.0.1:880; } Now just reload nginx:\n1 sudo service nginx force-reload Extras Zotero Reader - HTML5 client Zandy - Android Open Source client Tags: [zotero webdav nginx apache]","date":"2014-12-09T12:12:12Z","permalink":"https://balkian.com/p/zotero/","title":"Zotero"},{"content":"This is a quick note on proxying a local python application (e.g. flask) to a subdirectory in Apache. This assumes that the file wsgi.py contains a WSGI application with the name application. Hence, wsgi:application.\nGunicorn 1 2 3 4 5 \u0026lt;Location /myapp/\u0026gt; ProxyPass http://127.0.0.1:8888/myapp/ ProxyPassReverse http://127.0.0.1:8888/myapp/ RequestHeader set SCRIPT_NAME \u0026#34;/myapp/\u0026#34; \u0026lt;/Location\u0026gt; Important: SCRIPT_NAME and the end of ProxyPass URL MUST BE THE SAME. Otherwise, Gunicorn will fail miserably.\nTry it with:\n1 venv/bin/gunicorn -w 4 -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application UWSGI This is a very simple configuration. I will try to upload one with more options for uwsgi (in a .ini file).\n1 2 3 4 \u0026lt;Location /myapp/\u0026gt; SetHandler uwsgi_handler uWSGISocker 127.0.0.1:8888 \u0026lt;/Location\u0026gt; Try it with:\n1 uwsgi --socket 127.0.0.1:8888 -w wsgi:application Extra: Supervisor If everything went as expected, you can wrap your command in a supervisor config file and let it handle the server for you.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [unix_http_server] file=/tmp/myapp.sock ; path to your socket file [supervisord] logfile = %(here)s/logs/supervisor.log childlogdir = %(here)s/logs/ [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] logfile = %(here)s/logs/supervisorctl.log serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket [program:myapp] command = venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application directory = %(here)s environment = PATH=%(here)s/venv/bin/ logfile = %(here)s/logs/myapp.log Tags: [python apache proxy gunicorn uwsgi]","date":"2014-10-09T10:00:00Z","permalink":"https://balkian.com/p/proxies-with-apache-and-python/","title":"Proxies with Apache and python"},{"content":"Developing a python module and publishing it on Github is cool, but most of the times you want others to download and use it easily. That is the role of PyPi, the python package repository. In this post I show you how to publish your package in less than 10 minutes.\nChoose a fancy name If you haven\u0026rsquo;t done so yet, take a minute or two to think about this. To publish on PyPi you need a name for your package that isn\u0026rsquo;t taken. What\u0026rsquo;s more, a catchy and unique name will help people remember your module and feel more inclined to at least try it.\nThe package name should hint what your module does, but that\u0026rsquo;s not always the case. That\u0026rsquo;s your call. I personally put uniqueness and memorability over describing the functionality.\nCreate a .pypirc configuration file 1 2 3 4 5 6 7 8 9 10 11 12 13 [distutils] # this tells distutils what package indexes you can push to index-servers = pypi # the live PyPI pypitest # test PyPI [pypi] # authentication details for live PyPI repository = https://pypi.python.org/pypi username = { your_username } password = { your_password } # not necessary [pypitest] # authentication details for test PyPI repository = https://testpypi.python.org/pypi username = { your_username } As you can see, you need to register both in the main pypi repository and the testing server. The usernames and passwords might be different, that is up to you!\nPrepare your package This should be the structure:\n1 2 3 4 5 6 7 8 9 10 root-dir/ # Any name you want setup.py setup.cfg LICENSE.txt README.md mypackage/ __init__.py foo.py bar.py baz.py setup.cfg 1 2 [metadata] description-file = README.md The markdown README is the de facto standard in Github, but you can also use rST (reStructuredText), the standard in the python community.\nsetup.py 1 2 3 4 5 6 7 8 9 10 11 12 from distutils.core import setup setup(name = \u0026#39;mypackage\u0026#39;, packages = [\u0026#39;mypackage\u0026#39;], # this must be the same as the name above version = \u0026#39;{ version }\u0026#39;, description = \u0026#39;{ description }\u0026#39;, author = \u0026#39;{ name }\u0026#39;, email = \u0026#39;{ email }\u0026#39;, url = \u0026#39;https://github.com/{user}/{package}\u0026#39;, # URL to the github repo download_url = \u0026#39;https://github.com/{user}/{repo}/tarball/{version}\u0026#39;, keywords = [\u0026#39;websockets\u0026#39;, \u0026#39;display\u0026#39;, \u0026#39;d3\u0026#39;], # list of keywords that represent your package classifiers = [], ) You might notice that the download_url points to a Github URL. We could host our package anywhere, but Github is a convenient option. To create the tarball and the zip packages, you only need to tag a tag in your repository and push it to github:\n1 2 git tag {version} -m \u0026#34;{ Description of this tag/version}\u0026#34; git push --tags origin master Push to the testing/main pypi server It is advisable that you try your package on the test repository and fix any problems first. The process is simple:\n1 python setup.py register -r {pypitest/pypi} python setup.py sdist upload -r {pypitest/pypi} If everything went as expected, you can now install your package through pip and browse your package\u0026rsquo;s page. For instance, check my senpy package: https://pypi.python.org/pypi/senpy\n1 pip install senpy Tags: [github python pypi]","date":"2014-09-27T10:00:00Z","permalink":"https://balkian.com/p/publishing-on-pypi/","title":"Publishing on PyPi"},{"content":"As part of the OpeNER hackathon we decided to build a prototype that would allow us to compare how different countries feel about several topics. We used the OpeNER pipeline to get the sentiment from a set of newspaper articles we gathered from media in several languages. Then we aggregated those articles by category and country (using the source of the article or the language it was written in), obtaining the \u0026ldquo;overall feeling\u0026rdquo; of each country about each topic. Then, we used some fancy JavaScript to make sense out of the raw information.\nIt didn\u0026rsquo;t go too bad, it turns out we won.\nNow, it was time for a face-lift. I used this opportunity to play with new technologies and improve it:\nUsing Flask, this time using python 3.3 and Bootstrap 3.0 Cool HTML5+JS cards (thanks to pastetophone) Automatic generation of fake personal data to test the interface Obfuscation of personal emails The result can be seen here.\nPublishing a Python 3 app on Heroku 1 mkvirtualenv -p /usr/bin/python3.3 eurolovemap Since Heroku uses python 2.7 by default, we have to tell it which version we want, although it supports python 3.4 as well. I couldn\u0026rsquo;t get python 3.4 working using the deadsnakes ppa, so I used python 3.3 instead, which works fine but is not officially supported. Just create a file named runtime.txt in your project root, with the python version you want to use:\n1 python-3.3.1 Don\u0026rsquo;t forget to freeze your dependencies so Heroku can install them: bash pip freze \u0026gt; requirements.txt\nPublishing personal emails There are really sophisticated and effective ways to obfuscate personal emails so that spammers cannot easily grab yours. However, this time I needed something really simple to hide our emails from the simplest form of crawlers. Most of the team are in academia somehow, so in the end all our emails are available in sites like Google Scholar. Anyway, nobody likes getting spammed so I settled for a custom Caesar cipher. Please, don\u0026rsquo;t use it for any serious application if you are concerned about being spammed.\n1 2 def blur_email(email): return \u0026#34;\u0026#34;.join([chr(ord(i)+5) for i in email]) And this is the client side:\n1 2 3 4 5 6 7 8 9 10 11 12 window.onload = function(){ elems = document.getElementsByClassName(\u0026#39;profile-email\u0026#39;); for(var e in elems){ var blur = elems[e].innerHTML; var email = \u0026#34;\u0026#34;; for(var s in blur){ var a = blur.charCodeAt(s) email = email+String.fromCharCode(a-5); } elems[e].innerHTML = email; } } Unfortunately, this approach does not hide your email from anyone using PhantomJS, ZombieJS or similar. For that, other approaches like generating a picture with the address would be necessary. Nevertheless, it is overkill for a really simple ad-hoc application with custom formatting and just a bunch of emails that would easily be grabbed manually.\nGeneration of fake data To test the contact section of the site, I wanted to populate it with fake data. Fake-Factory is an amazing library that can generate fake data of almost any kind: emails, association names, acronyms\u0026hellip; It even lets you localise the results (get Spanish names, for instance) and generate factories for certain classes (à la Django).\nBut I also wanted pictures, enter Lorem Pixel. With its API you can generate pictures of almost any size, for different topics (e.g. nightlife, people) and with a custom text. You can even use an index, so it will always show the same picture.\nFor instance, the picture below is served through Lorem Pixel.\nBy the way, if you only want cat pictures, take a look at Placekitten. And for NSFW text, there\u0026rsquo;s the Samuel L. Jackson Ipsum\nTags: [javascript python heroku]","date":"2014-03-27T14:00:00Z","permalink":"https://balkian.com/p/updating-eurolovemap/","title":"Updating EuroLoveMap"},{"content":"A simple trick. If you want to remove all the \u0026lsquo;.swp\u0026rsquo; files from a git repository, just use:\n1 git rm --cached \u0026#39;**.swp\u0026#39; Tags: [git]","date":"2013-08-22T23:14:00Z","permalink":"https://balkian.com/p/remove-git-files-with-globbing/","title":"Remove git files with globbing"},{"content":"I\u0026rsquo;ve finally decided to set up a decent personal page. I have settled for github-pages because I like the idea of keeping my site in a repository and having someone else host and deploy it for me. The site will be really simple, mostly static files. Thanks to Github, Jekyll will automatically generate static pages for my posts every time I commit anything new to this repository.\nBut Jekyll can be used independently, so if I ever choose to host the site myself, I can do it quite easily. Another thing that I liked about this approach is that the generated html files can be used in the future, and I will not need Jekyll to serve it. Jekyll is really simple and most of the things are written in plain html. That means that everything could be easily reused if I ever choose to change to another blogging framework (e.g. pelical). But, for the time being, I like the fact that Github takes care of the compilation as well, so I can simply modify or add files through the web interface should I need to.\nI hadn\u0026rsquo;t played with HTML and CSS for a while now, so I also wanted to use this site as a playground. At some point, I realised I was doing mostly everything in plain HTML and CSS, and decided to keep it like that for as long as possible. As of this writing, I haven\u0026rsquo;t included any Javascript code in the page. Probably I will use some to add my gists and repositories, but we will see about that.\nI think the code speaks for itself, so you can check out my repository on Github. You can clone and deploy it easily like this:\n1 2 3 git clone https://github.com/balkian/balkian.github.com cd balkian.github.com jekyll serve -w I will keep updating this post with information about:\nSome Jekyll plugins that might be useful What CSS tricks I learnt The webfonts I used The badge on the left side of the page Tags: [starters javascript ruby github git]","date":"2013-08-22T14:14:22Z","permalink":"https://balkian.com/p/creating-my-web/","title":"Creating my web"},{"content":" 1 (font-lock-mode) Tags: [emacs org productivity lisp snippet]","date":"0001-01-01T00:00:00Z","permalink":"https://balkian.com/p/emacs-show-plain-text-version/","title":"Emacs: show plain text version"},{"content":"Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.\n1 2 3 4 5 6 hdmi_drive=2 hdmi_group=2 hdmi_mode=42 disable_overscan=1 config_hdmi_boost=7 Tags: [rpi snippet]","date":"0001-01-01T00:00:00Z","image":"https://balkian.com/img/rpi.png","permalink":"https://balkian.com/p/fixing-hdmi-flickering/","title":"Fixing HDMI flickering"}] \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..0f5b88d --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://balkian.com/2025-03-07T10:24:52+01:00https://balkian.com/post/2025-03-07T10:24:52+01:00https://balkian.com/p/rdf-is-dead/2025-03-07T10:24:52+01:00https://balkian.com/tags/semantic-web/2025-03-07T10:24:52+01:00https://balkian.com/tags/2025-03-07T10:24:52+01:00https://balkian.com/categories/2025-03-05T09:25:54+01:00https://balkian.com/tags/management/2025-03-05T09:25:54+01:00https://balkian.com/categories/reflections/2025-03-05T09:25:54+01:00https://balkian.com/tags/team/2025-03-05T09:25:54+01:00https://balkian.com/p/efficient-collaboration/2025-03-05T09:25:54+01:00https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/2025-02-26T23:22:59+01:00https://balkian.com/tags/json-ld/2025-02-26T23:22:59+01:00https://balkian.com/categories/programming/2025-02-26T23:22:59+01:00https://balkian.com/tags/pydantic/2025-02-26T23:22:59+01:00https://balkian.com/tags/python/2025-02-26T23:22:59+01:00https://balkian.com/tags/rdf/2025-02-26T23:22:59+01:00https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/2025-02-17T23:02:47+01:00https://balkian.com/tags/nix/2023-11-13T18:21:46+01:00https://balkian.com/p/nix-recipe-for-python-projects/2023-11-13T18:21:46+01:00https://balkian.com/p/kanata-advanced-keyboard-configuration/2023-01-20T18:11:00+00:00https://balkian.com/tags/keyboard/2023-01-20T18:11:00+00:00https://balkian.com/tags/layout/2023-01-20T18:11:00+00:00https://balkian.com/tags/linux/2023-01-20T18:11:00+00:00https://balkian.com/tags/logitech/2023-01-20T18:11:00+00:00https://balkian.com/tags/rust/2023-01-20T18:11:00+00:00https://balkian.com/archives/2022-03-06T00:00:00+00:00https://balkian.com/page/2022-03-06T00:00:00+00:00https://balkian.com/p/logitech-mb850-combi-in-linux/2021-10-30T00:00:01+00:00https://balkian.com/tags/mouse/2021-10-30T00:00:01+00:00https://balkian.com/p/logitech-mx-keys-for-mac-on-linux/2021-10-29T00:00:01+00:00https://balkian.com/tags/config/2019-06-01T00:00:01+00:00https://balkian.com/categories/linux/2019-06-01T00:00:01+00:00https://balkian.com/p/linux-on-the-microsoft-surface-go/2019-06-01T00:00:01+00:00https://balkian.com/tags/surface-go/2019-06-01T00:00:01+00:00https://balkian.com/p/controlling-zigbee-devices-with-mqtt/2019-01-06T10:00:00+00:00https://balkian.com/tags/iot/2019-01-06T10:00:00+00:00https://balkian.com/tags/mqtt/2019-01-06T10:00:00+00:00https://balkian.com/tags/zigbee/2019-01-06T10:00:00+00:00https://balkian.com/p/progress-bars-in-python/2016-09-28T18:47:00+00:00https://balkian.com/tags/dotfiles/2015-04-10T17:47:00+00:00https://balkian.com/tags/git/2015-04-10T17:47:00+00:00https://balkian.com/tags/github/2015-04-10T17:47:00+00:00https://balkian.com/p/sharing-dotfiles/2015-04-10T17:47:00+00:00https://balkian.com/tags/apache/2014-12-09T12:12:12+00:00https://balkian.com/tags/nginx/2014-12-09T12:12:12+00:00https://balkian.com/tags/webdav/2014-12-09T12:12:12+00:00https://balkian.com/p/zotero/2014-12-09T12:12:12+00:00https://balkian.com/tags/zotero/2014-12-09T12:12:12+00:00https://balkian.com/tags/gunicorn/2014-10-09T10:00:00+00:00https://balkian.com/p/proxies-with-apache-and-python/2014-10-09T10:00:00+00:00https://balkian.com/tags/proxy/2014-10-09T10:00:00+00:00https://balkian.com/tags/uwsgi/2014-10-09T10:00:00+00:00https://balkian.com/p/publishing-on-pypi/2014-09-27T10:00:00+00:00https://balkian.com/tags/pypi/2014-09-27T10:00:00+00:00https://balkian.com/tags/heroku/2014-03-27T14:00:00+00:00https://balkian.com/tags/javascript/2014-03-27T14:00:00+00:00https://balkian.com/p/updating-eurolovemap/2014-03-27T14:00:00+00:00https://balkian.com/p/remove-git-files-with-globbing/2013-08-22T23:14:00+00:00https://balkian.com/p/creating-my-web/2013-08-22T14:14:22+00:00https://balkian.com/tags/ruby/2013-08-22T14:14:22+00:00https://balkian.com/tags/starters/2013-08-22T14:14:22+00:00https://balkian.com/about/https://balkian.com/tags/arch/https://balkian.com/cheatsheet/https://balkian.com/tags/emacs/https://balkian.com/p/emacs-show-plain-text-version/https://balkian.com/p/fixing-hdmi-flickering/https://balkian.com/links/https://balkian.com/cheatsheet/linux/https://balkian.com/tags/lisp/https://balkian.com/tags/org/https://balkian.com/tags/productivity/https://balkian.com/tags/programming/https://balkian.com/projects/https://balkian.com/cheatsheet/python/https://balkian.com/tags/rpi/https://balkian.com/search/https://balkian.com/tags/snippet/ \ No newline at end of file diff --git a/tags/apache/index.html b/tags/apache/index.html new file mode 100644 index 0000000..b9f6f86 --- /dev/null +++ b/tags/apache/index.html @@ -0,0 +1,33 @@ +Tag: Apache - J. Fernando Sánchez +

Tags

2 pages

Apache

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/apache/index.xml b/tags/apache/index.xml new file mode 100644 index 0000000..18f5669 --- /dev/null +++ b/tags/apache/index.xml @@ -0,0 +1,246 @@ +Apache on J. Fernando Sánchezhttps://balkian.com/tags/apache/Recent content in Apache on J. Fernando SánchezHugo -- gohugo.ioen-usTue, 09 Dec 2014 12:12:12 +0000Zoterohttps://balkian.com/p/zotero/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/p/zotero/<p><a class="link" href="https://www.zotero.org/" target="_blank" rel="noopener" +>Zotero</a> is an Open Source tool that lets you +organise your bibliography, syncing it with the cloud. Unlike other +alternatives such as <a class="link" href="http://www.mendeley.com" target="_blank" rel="noopener" +>Mendeley</a>, Zotero can +upload the attachments and data to a private cloud via WebDav.</p> +<p>If you use nginx as your web server, know that even though it provides +partial support for webdav, Zotero needs more than that. Hence, you will +need another webdav server, and optionally let nginx proxy to it. This +short post provides the basics to get that set-up working under +Debian/Ubuntu.</p> +<h2 id="setting-up-apache">Setting up Apache +</h2><p>First we need to install Apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install apache2 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Change the head of &ldquo;/etc/apache2/sites-enabled/000-default&rdquo; to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;VirtualHost</span> <span class="s">*:880</span><span class="nt">&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Then, create a file /etc/apache2/sites-available/webdav:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nb">Alias</span> <span class="sx">/dav</span> <span class="sx">/home/webdav/dav</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/dav</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">on</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Order</span> Allow,Deny +</span></span><span class="line"><span class="cl"> <span class="nb">Allow</span> from <span class="k">all</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">On</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Options</span> +Indexes +</span></span><span class="line"><span class="cl"> <span class="nb">AuthType</span> Basic +</span></span><span class="line"><span class="cl"> <span class="nb">AuthName</span> DAV +</span></span><span class="line"><span class="cl"> <span class="nb">AuthBasicProvider</span> file +</span></span><span class="line"><span class="cl"> <span class="nb">AuthUserFile</span> <span class="sx">/home/webdav/.htpasswd</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Require</span> valid-user +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Ideally, you want your webdav folders to be private, adding +authentication to them. So you need to create the webdav and zotero +users and add the passwords to an htpasswd file. Even though you could +use a single user, since you will be configuring several clients with +your credentials I encourage you to create the zotero user as well. This +way you can always change the password for zotero without affecting any +other application using webdav.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo adduser webdav +</span></span><span class="line"><span class="cl">sudo htpasswd -c /home/webdav/.htpasswd webdav +</span></span><span class="line"><span class="cl">sudo htpasswd /home/webdav/.htpasswd zotero +</span></span><span class="line"><span class="cl">sudo mkdir -p /home/webdav/dav/zotero +</span></span></code></pre></td></tr></table> +</div> +</div><p>Enable the site and restart apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo a2enmod webdav +</span></span><span class="line"><span class="cl">sudo a2enmod dav_fs +</span></span><span class="line"><span class="cl">sudo a2ensite webdav +</span></span><span class="line"><span class="cl">sudo service apache2 restart +</span></span></code></pre></td></tr></table> +</div> +</div><p>At this point everything should be working at +<a class="link" href="http://" target="_blank" rel="noopener" +>http://</a>&lt;your_host&gt;:880/dav/zotero</p> +<h2 id="setting-up-nginx">Setting up NGINX +</h2><p>After the Apache side is working, we can use nginx as a proxy to get +cleaner URIs. In your desired site/location, add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/dav</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="kn">client_max_body_size</span> <span class="s">20M</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:880</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now just reload nginx:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo service nginx force-reload +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extras">Extras +</h2><ul> +<li><a class="link" href="http://zoteroreader.com/" target="_blank" rel="noopener" +>Zotero Reader</a> - HTML5 client</li> +<li><a class="link" href="https://github.com/ajlyon/zandy" target="_blank" rel="noopener" +>Zandy</a> - Android Open Source +client</li> +</ul>Proxies with Apache and pythonhttps://balkian.com/p/proxies-with-apache-and-python/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/p/proxies-with-apache-and-python/<p>This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name <em>application</em>. Hence, wsgi:application.</p> +<h2 id="gunicorn">Gunicorn +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPass</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPassReverse</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">RequestHeader</span> set SCRIPT_NAME <span class="s2">&#34;/myapp/&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p><strong>Important</strong>: <em>SCRIPT_NAME</em> and the end of <em>ProxyPass</em> URL <strong>MUST BE +THE SAME</strong>. Otherwise, Gunicorn will fail miserably.</p> +<p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">venv/bin/gunicorn -w <span class="m">4</span> -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="uwsgi">UWSGI +</h2><p>This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">SetHandler</span> uwsgi_handler +</span></span><span class="line"><span class="cl"> <span class="nb">uWSGISocker</span> <span class="m">127.0.0.1</span>:8888 +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uwsgi --socket 127.0.0.1:8888 -w wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extra-supervisor">Extra: Supervisor +</h2><p>If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[unix_http_server]</span> +</span></span><span class="line"><span class="cl"><span class="na">file</span><span class="o">=</span><span class="s">/tmp/myapp.sock ; path to your socket file</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisord]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisor.log</span> +</span></span><span class="line"><span class="cl"><span class="na">childlogdir</span> <span class="o">=</span> <span class="s">%(here)s/logs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[rpcinterface:supervisor]</span> +</span></span><span class="line"><span class="cl"><span class="na">supervisor.rpcinterface_factory</span> <span class="o">=</span> <span class="s">supervisor.rpcinterface:make_main_rpcinterface</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisorctl]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisorctl.log</span> +</span></span><span class="line"><span class="cl"><span class="na">serverurl</span><span class="o">=</span><span class="s">unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[program:myapp]</span> +</span></span><span class="line"><span class="cl"><span class="na">command</span> <span class="o">=</span> <span class="s">venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application</span> +</span></span><span class="line"><span class="cl"><span class="na">directory</span> <span class="o">=</span> <span class="s">%(here)s</span> +</span></span><span class="line"><span class="cl"><span class="na">environment</span> <span class="o">=</span> <span class="s">PATH=%(here)s/venv/bin/</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/myapp.log</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/apache/page/1/index.html b/tags/apache/page/1/index.html new file mode 100644 index 0000000..e88dfd4 --- /dev/null +++ b/tags/apache/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/apache/ + \ No newline at end of file diff --git a/tags/arch/index.html b/tags/arch/index.html new file mode 100644 index 0000000..b1fb52a --- /dev/null +++ b/tags/arch/index.html @@ -0,0 +1,33 @@ +Tag: Arch - J. Fernando Sánchez +

Tags

1 page

Arch

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/arch/index.xml b/tags/arch/index.xml new file mode 100644 index 0000000..4cc6e61 --- /dev/null +++ b/tags/arch/index.xml @@ -0,0 +1,151 @@ +Arch on J. Fernando Sánchezhttps://balkian.com/tags/arch/Recent content in Arch on J. Fernando SánchezHugo -- gohugo.ioen-usLinux Cheatsheethttps://balkian.com/cheatsheet/linux/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/linux/<img src="https://balkian.com/img/linux.png" alt="Featured image of post Linux Cheatsheet" /><h2 id="black-screen-and-lightdm-doesnt-unlock">Black screen and LightDM doesn&rsquo;t unlock +</h2><p>Add this to your /etc/lightdm/lightdm.conf file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[LightDM]</span> +</span></span><span class="line"><span class="cl"><span class="na">logind-check-graphical</span><span class="o">=</span><span class="s">true</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>##Edit previous commands</p> +<p><code>fc</code> is a shell builtin to list and edit previous commands in an editor. +In addition to editing a single line (which you can also do with <code>C-x C-e</code>), it also allows you to edit and run several lines at the same time. +You use it like this:</p> +<p>List previous commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10259</span> nvim deploy.sh +</span></span><span class="line"><span class="cl">10260* <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> <span class="nb">cd</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>List commands with date (in zsh)</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -ld +</span></span><span class="line"><span class="cl">10260* 19:38 <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* 19:38 nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 19:40 <span class="nb">fc</span> -l +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can add the date too:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -fld +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 1/10/2019 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 1/10/2019 19:40 <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10264</span> 1/10/2019 19:40 <span class="nb">fc</span> -ld +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can edit a range of commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> <span class="m">10262</span> <span class="m">10264</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The range can be relative to the current position, so the previous command is equivalent to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -3 -1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>If you save and exit, all commands are executed as a script, and it will be added to your history.</p> +<p>Source: <a class="link" href="https://shapeshed.com/unix-fc/" target="_blank" rel="noopener" +>https://shapeshed.com/unix-fc/</a></p> +<h2 id="prevent-logoff-from-killing-tmux-sessions">Prevent logoff from killing tmux sessions +</h2><p>Lately I&rsquo;ve noticed that logging out of i3, intentionally or when i3 fails, would also kill any tmux or emacs sessions. +This is extremely annoying.</p> +<p>This is caused by a new default in logind (systemd&rsquo;s login) to kill user process on logoff. +You can revert this setting in your logind.conf (<code>/etc/systemd/logind.conf</code>):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">KillUserProcesses</span><span class="o">=</span><span class="s">no</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Or only for a specific process (e.g., tmux):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemd-run --scope --user tmux +</span></span></code></pre></td></tr></table> +</div> +</div><p>Source: <a class="link" href="https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session" target="_blank" rel="noopener" +>https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session</a></p> +<h2 id="upload-a-temporary-file">Upload a temporary file +</h2><p>Sometimes you just need to copy/paste a file from a server, and copying from the terminal can be a hassle. +These two services are command-line &ldquo;pastebins&rdquo; just one curl away:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span><span class="lnt">8 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F <span class="s1">&#39;sprunge=&lt;-&#39;</span> http://sprunge.us +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> curl -F <span class="s1">&#39;f:1=&lt;-&#39;</span> ix.io +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F<span class="s2">&#34;file=@-&#34;</span> https://ttm.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h1 id="install-fortinet-sslvpn-support-for-networkmanager">Install Fortinet SSLVPN support for NetworkManager +</h1><p>UPM (Universidad Politécnica de Madrid) uses a propriatary VPN solution. +The instructions for GNU/Linux on their website involve downloading a specific client (<code>.tar.gz</code>) and manually running it. +That works, but it is kind of a hassle. +A much more convenient alternative is installing this NetworkManager plugin:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pacman -Sy networkmanager-fortisslvpn +</span></span><span class="line"><span class="cl"><span class="c1"># Or apt get install networkmanager-fortisslvpn </span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now you can simply add a new VPN connection in NetworkManager and manage it as you would any other connection.</p> \ No newline at end of file diff --git a/tags/arch/page/1/index.html b/tags/arch/page/1/index.html new file mode 100644 index 0000000..1ed9e13 --- /dev/null +++ b/tags/arch/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/arch/ + \ No newline at end of file diff --git a/tags/config/index.html b/tags/config/index.html new file mode 100644 index 0000000..75279a1 --- /dev/null +++ b/tags/config/index.html @@ -0,0 +1,33 @@ +Tag: Config - J. Fernando Sánchez +

Tags

1 page

Config

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/config/index.xml b/tags/config/index.xml new file mode 100644 index 0000000..720364f --- /dev/null +++ b/tags/config/index.xml @@ -0,0 +1,80 @@ +Config on J. Fernando Sánchezhttps://balkian.com/tags/config/Recent content in Config on J. Fernando SánchezHugo -- gohugo.ioen-usSat, 01 Jun 2019 00:00:01 +0000Linux on the Microsoft Surface Gohttps://balkian.com/p/linux-on-the-microsoft-surface-go/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/p/linux-on-the-microsoft-surface-go/<p>Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. +These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.</p> +<h2 id="installing-the-kernel">Installing the kernel +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --depth <span class="m">1</span> https://github.com/jakeday/linux-surface.git ~/linux-surface +</span></span><span class="line"><span class="cl">cp -a ~/linux-surface /media/&lt;your usb&gt; +</span></span></code></pre></td></tr></table> +</div> +</div><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -a /media/&lt;your usb&gt;/linux-surface ~/ +</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/linux-surface/ +</span></span><span class="line"><span class="cl">sudo sh setup.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="booting-ubuntu-first">Booting ubuntu first +</h2><p>Switch out of Windows S mode.</p> +<p>Boot into the &ldquo;Command Prompt&rdquo;.</p> +<p>From Windows go to &ldquo;change advanced startup options&rdquo; and select &ldquo;restart now&rdquo;.</p> +<p>When it reboots, choose the &ldquo;Troubleshoot&rdquo; option, then choose the &ldquo;Advanced options&rdquo; option, and finally choose the &ldquo;Command Prompt&rdquo; option.</p> +<p>After the device reboots, login to the command prompt and then you should see a terminal with X:\windows\system32&gt;</p> +<p>At the prompt, check your UEFI entries:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /enum firmware +</span></span></code></pre></td></tr></table> +</div> +</div><p>Copy UEFI entry of &ldquo;Windows Boot Manager&rdquo; to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d &ldquo;Ubuntu&rdquo;</p> +<p>Copy the printed GUID number including the braces {} using Ctrl+C</p> +<p>Set file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \EFI\ubuntu\grubx64.efi</p> +<p>Set Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /set <span class="o">{</span>fwbootmgr<span class="o">}</span> displayorder <span class="o">{</span>guid<span class="o">}</span> /addfirst +</span></span></code></pre></td></tr></table> +</div> +</div><p>Check your UEFI entries again: bcdedit /enum firmware You should see something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">Firmware Boot Manager +</span></span><span class="line"><span class="cl">--------------------- +</span></span><span class="line"><span class="cl">identifier <span class="o">{</span>fwbootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl">displayorder <span class="o">{</span>3510232e-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>bootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>2148799b-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a67-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a68-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl">timeout <span class="m">0</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.</p> \ No newline at end of file diff --git a/tags/config/page/1/index.html b/tags/config/page/1/index.html new file mode 100644 index 0000000..69d923e --- /dev/null +++ b/tags/config/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/config/ + \ No newline at end of file diff --git a/tags/dotfiles/index.html b/tags/dotfiles/index.html new file mode 100644 index 0000000..b34d263 --- /dev/null +++ b/tags/dotfiles/index.html @@ -0,0 +1,33 @@ +Tag: Dotfiles - J. Fernando Sánchez +

Tags

1 page

Dotfiles

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/dotfiles/index.xml b/tags/dotfiles/index.xml new file mode 100644 index 0000000..57ed4c5 --- /dev/null +++ b/tags/dotfiles/index.xml @@ -0,0 +1,39 @@ +Dotfiles on J. Fernando Sánchezhttps://balkian.com/tags/dotfiles/Recent content in Dotfiles on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 10 Apr 2015 17:47:00 +0000Sharing dotfileshttps://balkian.com/p/sharing-dotfiles/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/p/sharing-dotfiles/<p>Today&rsquo;s post is half a quick note, half public shaming. In other words, +it is a reminder to be very careful with OAuth tokens and passwords.</p> +<p>As part of moving to emacs, I starting using the incredibly useful +<a class="link" href="https://github.com/defunkt/gist.el" target="_blank" rel="noopener" +>gh.el</a>. When you first use it, the +extension saves either your password or an OAuth token in your +.gitconfig file. This is cool and convenient, unless you <a class="link" href="https://github.com/balkian/dotfiles" target="_blank" rel="noopener" +>happen to be +publishing your .gitconfig file in a public +repo</a>.</p> +<p>So, how can you still share your gitconfig without sharing your +password/token with the rest of the world? Since Git 1.7.0, you can +<a class="link" href="http://stackoverflow.com/questions/1557183/is-it-possible-to-include-a-file-in-your-gitconfig" target="_blank" rel="noopener" +>include other files in your +gitconfig</a>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[include] +</span></span><span class="line"><span class="cl"> path = ~/.gitconfig_secret +</span></span></code></pre></td></tr></table> +</div> +</div><p>And now, in your .gitconfig_secret file, you just have to add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[github] +</span></span><span class="line"><span class="cl"> user = balkian +</span></span><span class="line"><span class="cl"> token = &#34;&lt; Your secret token &gt;&#34; +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/dotfiles/page/1/index.html b/tags/dotfiles/page/1/index.html new file mode 100644 index 0000000..c3babd8 --- /dev/null +++ b/tags/dotfiles/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/dotfiles/ + \ No newline at end of file diff --git a/tags/emacs/index.html b/tags/emacs/index.html new file mode 100644 index 0000000..b8c56db --- /dev/null +++ b/tags/emacs/index.html @@ -0,0 +1,33 @@ +Tag: Emacs - J. Fernando Sánchez +

Tags

1 page

Emacs

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/emacs/index.xml b/tags/emacs/index.xml new file mode 100644 index 0000000..be099ed --- /dev/null +++ b/tags/emacs/index.xml @@ -0,0 +1,9 @@ +Emacs on J. Fernando Sánchezhttps://balkian.com/tags/emacs/Recent content in Emacs on J. Fernando SánchezHugo -- gohugo.ioen-usEmacs: show plain text versionhttps://balkian.com/p/emacs-show-plain-text-version/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/emacs-show-plain-text-version/<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">font-lock-mode</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/emacs/page/1/index.html b/tags/emacs/page/1/index.html new file mode 100644 index 0000000..a2fa578 --- /dev/null +++ b/tags/emacs/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/emacs/ + \ No newline at end of file diff --git a/tags/git/index.html b/tags/git/index.html new file mode 100644 index 0000000..63f6436 --- /dev/null +++ b/tags/git/index.html @@ -0,0 +1,33 @@ +Tag: Git - J. Fernando Sánchez +

Tags

3 pages

Git

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/git/index.xml b/tags/git/index.xml new file mode 100644 index 0000000..9ede261 --- /dev/null +++ b/tags/git/index.xml @@ -0,0 +1,96 @@ +Git on J. Fernando Sánchezhttps://balkian.com/tags/git/Recent content in Git on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 10 Apr 2015 17:47:00 +0000Sharing dotfileshttps://balkian.com/p/sharing-dotfiles/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/p/sharing-dotfiles/<p>Today&rsquo;s post is half a quick note, half public shaming. In other words, +it is a reminder to be very careful with OAuth tokens and passwords.</p> +<p>As part of moving to emacs, I starting using the incredibly useful +<a class="link" href="https://github.com/defunkt/gist.el" target="_blank" rel="noopener" +>gh.el</a>. When you first use it, the +extension saves either your password or an OAuth token in your +.gitconfig file. This is cool and convenient, unless you <a class="link" href="https://github.com/balkian/dotfiles" target="_blank" rel="noopener" +>happen to be +publishing your .gitconfig file in a public +repo</a>.</p> +<p>So, how can you still share your gitconfig without sharing your +password/token with the rest of the world? Since Git 1.7.0, you can +<a class="link" href="http://stackoverflow.com/questions/1557183/is-it-possible-to-include-a-file-in-your-gitconfig" target="_blank" rel="noopener" +>include other files in your +gitconfig</a>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[include] +</span></span><span class="line"><span class="cl"> path = ~/.gitconfig_secret +</span></span></code></pre></td></tr></table> +</div> +</div><p>And now, in your .gitconfig_secret file, you just have to add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[github] +</span></span><span class="line"><span class="cl"> user = balkian +</span></span><span class="line"><span class="cl"> token = &#34;&lt; Your secret token &gt;&#34; +</span></span></code></pre></td></tr></table> +</div> +</div>Remove git files with globbinghttps://balkian.com/p/remove-git-files-with-globbing/Thu, 22 Aug 2013 23:14:00 +0000https://balkian.com/p/remove-git-files-with-globbing/<p>A simple trick. If you want to remove all the &lsquo;.swp&rsquo; files from a git +repository, just use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git rm --cached <span class="s1">&#39;**.swp&#39;</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Creating my webhttps://balkian.com/p/creating-my-web/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/p/creating-my-web/<p>I&rsquo;ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +<a class="link" href="http://jekyllrb.com" target="_blank" rel="noopener" +>Jekyll</a> will automatically generate static pages +for my posts every time I commit anything new to this repository.</p> +<p>But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.</p> +<p>I hadn&rsquo;t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven&rsquo;t included +any Javascript code in the page. Probably I will use some to add my +<a class="link" href="http://gist.github.com/balkian" target="_blank" rel="noopener" +>gists</a> and +<a class="link" href="http://github.com/balkian" target="_blank" rel="noopener" +>repositories</a>, but we will see about that.</p> +<p>I think the code speaks for itself, so you can check out <a class="link" href="http://github.com/balkian/balkian.github.com" target="_blank" rel="noopener" +>my repository +on Github</a>. You can clone +and deploy it easily like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone +</span></span><span class="line"><span class="cl">https://github.com/balkian/balkian.github.com <span class="nb">cd</span> balkian.github.com +</span></span><span class="line"><span class="cl">jekyll serve -w +</span></span></code></pre></td></tr></table> +</div> +</div><p>I will keep updating this post with information about:</p> +<ul> +<li>Some Jekyll plugins that might be useful</li> +<li>What CSS tricks I learnt</li> +<li>The webfonts I used</li> +<li>The badge on the left side of the page</li> +</ul> \ No newline at end of file diff --git a/tags/git/page/1/index.html b/tags/git/page/1/index.html new file mode 100644 index 0000000..c2c6727 --- /dev/null +++ b/tags/git/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/git/ + \ No newline at end of file diff --git a/tags/github/index.html b/tags/github/index.html new file mode 100644 index 0000000..5b23123 --- /dev/null +++ b/tags/github/index.html @@ -0,0 +1,33 @@ +Tag: Github - J. Fernando Sánchez +

Tags

3 pages

Github

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/github/index.xml b/tags/github/index.xml new file mode 100644 index 0000000..61752ed --- /dev/null +++ b/tags/github/index.xml @@ -0,0 +1,247 @@ +Github on J. Fernando Sánchezhttps://balkian.com/tags/github/Recent content in Github on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 10 Apr 2015 17:47:00 +0000Sharing dotfileshttps://balkian.com/p/sharing-dotfiles/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/p/sharing-dotfiles/<p>Today&rsquo;s post is half a quick note, half public shaming. In other words, +it is a reminder to be very careful with OAuth tokens and passwords.</p> +<p>As part of moving to emacs, I starting using the incredibly useful +<a class="link" href="https://github.com/defunkt/gist.el" target="_blank" rel="noopener" +>gh.el</a>. When you first use it, the +extension saves either your password or an OAuth token in your +.gitconfig file. This is cool and convenient, unless you <a class="link" href="https://github.com/balkian/dotfiles" target="_blank" rel="noopener" +>happen to be +publishing your .gitconfig file in a public +repo</a>.</p> +<p>So, how can you still share your gitconfig without sharing your +password/token with the rest of the world? Since Git 1.7.0, you can +<a class="link" href="http://stackoverflow.com/questions/1557183/is-it-possible-to-include-a-file-in-your-gitconfig" target="_blank" rel="noopener" +>include other files in your +gitconfig</a>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[include] +</span></span><span class="line"><span class="cl"> path = ~/.gitconfig_secret +</span></span></code></pre></td></tr></table> +</div> +</div><p>And now, in your .gitconfig_secret file, you just have to add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[github] +</span></span><span class="line"><span class="cl"> user = balkian +</span></span><span class="line"><span class="cl"> token = &#34;&lt; Your secret token &gt;&#34; +</span></span></code></pre></td></tr></table> +</div> +</div>Publishing on PyPihttps://balkian.com/p/publishing-on-pypi/Sat, 27 Sep 2014 10:00:00 +0000https://balkian.com/p/publishing-on-pypi/<p>Developing a python module and publishing it on Github is cool, but most +of the times you want others to download and use it easily. That is the +role of PyPi, the python package repository. In this post I show you how +to publish your package in less than 10 minutes.</p> +<h2 id="choose-a-fancy-name">Choose a fancy name +</h2><p>If you haven&rsquo;t done so yet, take a minute or two to think about this. +To publish on PyPi you need a name for your package that isn&rsquo;t taken. +What&rsquo;s more, a catchy and unique name will help people remember your +module and feel more inclined to at least try it.</p> +<p>The package name should hint what your module does, but that&rsquo;s not +always the case. That&rsquo;s your call. I personally put uniqueness and +memorability over describing the functionality.</p> +<h2 id="create-a-pypirc-configuration-file">Create a .pypirc configuration file +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">[distutils] # this tells distutils what package indexes you can push to</span> +</span></span><span class="line"><span class="cl"><span class="na">index-servers</span> <span class="o">=</span><span class="s"> +</span></span></span><span class="line"><span class="cl"><span class="s"> pypi # the live PyPI +</span></span></span><span class="line"><span class="cl"><span class="s"> pypitest # test PyPI</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypi] # authentication details for live PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://pypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span><span class="line"><span class="cl"><span class="na">password</span> <span class="o">=</span> <span class="s">{ your_password } # not necessary</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypitest] # authentication details for test PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://testpypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>As you can see, you need to register both in the <a class="link" href="https://pypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>main pypi +repository</a> and +the <a class="link" href="https://testpypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>testing +server</a>. The +usernames and passwords might be different, that is up to you!</p> +<h2 id="prepare-your-package">Prepare your package +</h2><p>This should be the structure:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">root-dir/ # Any name you want +</span></span><span class="line"><span class="cl"> setup.py +</span></span><span class="line"><span class="cl"> setup.cfg +</span></span><span class="line"><span class="cl"> LICENSE.txt +</span></span><span class="line"><span class="cl"> README.md +</span></span><span class="line"><span class="cl"> mypackage/ +</span></span><span class="line"><span class="cl"> __init__.py +</span></span><span class="line"><span class="cl"> foo.py +</span></span><span class="line"><span class="cl"> bar.py +</span></span><span class="line"><span class="cl"> baz.py +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="setupcfg">setup.cfg +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[metadata]</span> +</span></span><span class="line"><span class="cl"><span class="na">description-file</span> <span class="o">=</span> <span class="s">README.md</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The markdown README is the <em>de facto</em> standard in Github, but you can +also use rST (reStructuredText), the standard in the python community.</p> +<h3 id="setuppy">setup.py +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">distutils.core</span> <span class="kn">import</span> <span class="n">setup</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">setup</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;mypackage&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;mypackage&#39;</span><span class="p">],</span> <span class="c1"># this must be the same as the name above</span> +</span></span><span class="line"><span class="cl"> <span class="n">version</span> <span class="o">=</span> <span class="s1">&#39;{ version }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">description</span> <span class="o">=</span> <span class="s1">&#39;{ description }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">author</span> <span class="o">=</span> <span class="s1">&#39;{ name }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">email</span> <span class="o">=</span> <span class="s1">&#39;{ email }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{package}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="c1"># URL to the github repo</span> +</span></span><span class="line"><span class="cl"> <span class="n">download_url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{repo}</span><span class="s1">/tarball/</span><span class="si">{version}</span><span class="s1">&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">keywords</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;websockets&#39;</span><span class="p">,</span> <span class="s1">&#39;display&#39;</span><span class="p">,</span> <span class="s1">&#39;d3&#39;</span><span class="p">],</span> <span class="c1"># list of keywords that represent your package</span> +</span></span><span class="line"><span class="cl"> <span class="n">classifiers</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>You might notice that the download_url points to a Github URL. We could +host our package anywhere, but Github is a convenient option. To create +the tarball and the zip packages, you only need to tag a tag in your +repository and push it to github:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git tag <span class="o">{</span>version<span class="o">}</span> -m <span class="s2">&#34;{ Description of this tag/version}&#34;</span> +</span></span><span class="line"><span class="cl">git push --tags origin master +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="push-to-the-testingmain-pypi-server">Push to the testing/main pypi server +</h2><p>It is advisable that you try your package on the test repository and fix +any problems first. The process is simple:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">python setup.py register -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> python setup.py sdist upload -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>If everything went as expected, you can now install your package through +pip and browse your package&rsquo;s page. For instance, check my senpy +package: <a class="link" href="https://pypi.python.org/pypi/senpy" target="_blank" rel="noopener" +>https://pypi.python.org/pypi/senpy</a></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install senpy +</span></span></code></pre></td></tr></table> +</div> +</div>Creating my webhttps://balkian.com/p/creating-my-web/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/p/creating-my-web/<p>I&rsquo;ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +<a class="link" href="http://jekyllrb.com" target="_blank" rel="noopener" +>Jekyll</a> will automatically generate static pages +for my posts every time I commit anything new to this repository.</p> +<p>But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.</p> +<p>I hadn&rsquo;t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven&rsquo;t included +any Javascript code in the page. Probably I will use some to add my +<a class="link" href="http://gist.github.com/balkian" target="_blank" rel="noopener" +>gists</a> and +<a class="link" href="http://github.com/balkian" target="_blank" rel="noopener" +>repositories</a>, but we will see about that.</p> +<p>I think the code speaks for itself, so you can check out <a class="link" href="http://github.com/balkian/balkian.github.com" target="_blank" rel="noopener" +>my repository +on Github</a>. You can clone +and deploy it easily like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone +</span></span><span class="line"><span class="cl">https://github.com/balkian/balkian.github.com <span class="nb">cd</span> balkian.github.com +</span></span><span class="line"><span class="cl">jekyll serve -w +</span></span></code></pre></td></tr></table> +</div> +</div><p>I will keep updating this post with information about:</p> +<ul> +<li>Some Jekyll plugins that might be useful</li> +<li>What CSS tricks I learnt</li> +<li>The webfonts I used</li> +<li>The badge on the left side of the page</li> +</ul> \ No newline at end of file diff --git a/tags/github/page/1/index.html b/tags/github/page/1/index.html new file mode 100644 index 0000000..48b370f --- /dev/null +++ b/tags/github/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/github/ + \ No newline at end of file diff --git a/tags/gunicorn/index.html b/tags/gunicorn/index.html new file mode 100644 index 0000000..95917cc --- /dev/null +++ b/tags/gunicorn/index.html @@ -0,0 +1,33 @@ +Tag: Gunicorn - J. Fernando Sánchez +

Tags

1 page

Gunicorn

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/gunicorn/index.xml b/tags/gunicorn/index.xml new file mode 100644 index 0000000..77cc49e --- /dev/null +++ b/tags/gunicorn/index.xml @@ -0,0 +1,105 @@ +Gunicorn on J. Fernando Sánchezhttps://balkian.com/tags/gunicorn/Recent content in Gunicorn on J. Fernando SánchezHugo -- gohugo.ioen-usThu, 09 Oct 2014 10:00:00 +0000Proxies with Apache and pythonhttps://balkian.com/p/proxies-with-apache-and-python/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/p/proxies-with-apache-and-python/<p>This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name <em>application</em>. Hence, wsgi:application.</p> +<h2 id="gunicorn">Gunicorn +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPass</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPassReverse</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">RequestHeader</span> set SCRIPT_NAME <span class="s2">&#34;/myapp/&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p><strong>Important</strong>: <em>SCRIPT_NAME</em> and the end of <em>ProxyPass</em> URL <strong>MUST BE +THE SAME</strong>. Otherwise, Gunicorn will fail miserably.</p> +<p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">venv/bin/gunicorn -w <span class="m">4</span> -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="uwsgi">UWSGI +</h2><p>This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">SetHandler</span> uwsgi_handler +</span></span><span class="line"><span class="cl"> <span class="nb">uWSGISocker</span> <span class="m">127.0.0.1</span>:8888 +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uwsgi --socket 127.0.0.1:8888 -w wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extra-supervisor">Extra: Supervisor +</h2><p>If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[unix_http_server]</span> +</span></span><span class="line"><span class="cl"><span class="na">file</span><span class="o">=</span><span class="s">/tmp/myapp.sock ; path to your socket file</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisord]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisor.log</span> +</span></span><span class="line"><span class="cl"><span class="na">childlogdir</span> <span class="o">=</span> <span class="s">%(here)s/logs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[rpcinterface:supervisor]</span> +</span></span><span class="line"><span class="cl"><span class="na">supervisor.rpcinterface_factory</span> <span class="o">=</span> <span class="s">supervisor.rpcinterface:make_main_rpcinterface</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisorctl]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisorctl.log</span> +</span></span><span class="line"><span class="cl"><span class="na">serverurl</span><span class="o">=</span><span class="s">unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[program:myapp]</span> +</span></span><span class="line"><span class="cl"><span class="na">command</span> <span class="o">=</span> <span class="s">venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application</span> +</span></span><span class="line"><span class="cl"><span class="na">directory</span> <span class="o">=</span> <span class="s">%(here)s</span> +</span></span><span class="line"><span class="cl"><span class="na">environment</span> <span class="o">=</span> <span class="s">PATH=%(here)s/venv/bin/</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/myapp.log</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/gunicorn/page/1/index.html b/tags/gunicorn/page/1/index.html new file mode 100644 index 0000000..2fc5d94 --- /dev/null +++ b/tags/gunicorn/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/gunicorn/ + \ No newline at end of file diff --git a/tags/heroku/index.html b/tags/heroku/index.html new file mode 100644 index 0000000..aa331a3 --- /dev/null +++ b/tags/heroku/index.html @@ -0,0 +1,33 @@ +Tag: Heroku - J. Fernando Sánchez +

Tags

1 page

Heroku

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/heroku/index.xml b/tags/heroku/index.xml new file mode 100644 index 0000000..b62f8cb --- /dev/null +++ b/tags/heroku/index.xml @@ -0,0 +1,135 @@ +Heroku on J. Fernando Sánchezhttps://balkian.com/tags/heroku/Recent content in Heroku on J. Fernando SánchezHugo -- gohugo.ioen-usThu, 27 Mar 2014 14:00:00 +0000Updating EuroLoveMaphttps://balkian.com/p/updating-eurolovemap/Thu, 27 Mar 2014 14:00:00 +0000https://balkian.com/p/updating-eurolovemap/<p>As part of the <a class="link" href="http://www.opener-project.org/2013/07/18/opener-hackathon-in-amsterdam/" target="_blank" rel="noopener" +>OpeNER +hackathon</a> +we decided to build a prototype that would allow us to compare how +different countries feel about several topics. We used the OpeNER +pipeline to get the sentiment from a set of newspaper articles we +gathered from media in several languages. Then we aggregated those +articles by category and country (using the source of the article or the +language it was written in), obtaining the &ldquo;overall feeling&rdquo; of each +country about each topic. Then, we used some fancy JavaScript to make +sense out of the raw information.</p> +<p>It didn&rsquo;t go too bad, it turns out <a class="link" href="http://eurosentiment.eu/wp-content/uploads/2013/07/BOLv9qnCIAAJEek.jpg" target="_blank" rel="noopener" +>we +won</a>.</p> +<p>Now, it was time for a face-lift. I used this opportunity to play with +new technologies and improve it:</p> +<ul> +<li>Using Flask, this time using python 3.3 and Bootstrap 3.0</li> +<li>Cool HTML5+JS cards (thanks to +<a class="link" href="http://pastetophone.com" target="_blank" rel="noopener" +>pastetophone</a>)</li> +<li>Automatic generation of fake personal data to test the interface</li> +<li>Obfuscation of personal emails</li> +</ul> +<p>The result can be <a class="link" href="http://eurolovemap.herokuapp.com/" target="_blank" rel="noopener" +>seen here</a>.</p> +<h2 id="publishing-a-python-3-app-on-heroku">Publishing a Python 3 app on Heroku +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkvirtualenv -p /usr/bin/python3.3 eurolovemap +</span></span></code></pre></td></tr></table> +</div> +</div><p>Since Heroku uses python 2.7 by default, we have to tell it which +version we want, although it supports python 3.4 as well. I couldn&rsquo;t +get python 3.4 working using the +<a class="link" href="https://launchpad.net/~fkrull/&#43;archive/deadsnakes" target="_blank" rel="noopener" +>deadsnakes</a> ppa, so +I used python 3.3 instead, which works fine but is not officially +supported. Just create a file named <em>runtime.txt</em> in your project root, +with the python version you want to use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python-3.3.1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Don&rsquo;t forget to freeze your dependencies so Heroku can install them: +<code>bash pip freze &gt; requirements.txt</code></p> +<h2 id="publishing-personal-emails">Publishing personal emails +</h2><p>There are really sophisticated and effective ways to obfuscate personal +emails so that spammers cannot easily grab yours. However, this time I +needed something really simple to hide our emails from the simplest form +of crawlers. Most of the team are in academia somehow, so in the end all +our emails are available in sites like Google Scholar. Anyway, nobody +likes getting spammed so I settled for a custom <a class="link" href="http://en.wikipedia.org/wiki/Caesar_cipher" target="_blank" rel="noopener" +>Caesar +cipher</a>. Please, don&rsquo;t use +it for any serious application if you are concerned about being spammed.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">blur_email</span><span class="p">(</span><span class="n">email</span><span class="p">):</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">email</span><span class="p">])</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>And this is the client side:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">&#39;profile-email&#39;</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">e</span> <span class="k">in</span> <span class="nx">elems</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">blur</span> <span class="o">=</span> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">email</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">s</span> <span class="k">in</span> <span class="nx">blur</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">blur</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="o">+</span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="nx">a</span><span class="o">-</span><span class="mi">5</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Unfortunately, this approach does not hide your email from anyone using +<a class="link" href="http://phantomjs.org/" target="_blank" rel="noopener" +>PhantomJS</a>, +<a class="link" href="http://zombie.labnotes.org/" target="_blank" rel="noopener" +>ZombieJS</a> or similar. For that, other +approaches like generating a picture with the address would be +necessary. Nevertheless, it is overkill for a really simple ad-hoc +application with custom formatting and just a bunch of emails that would +easily be grabbed manually.</p> +<h2 id="generation-of-fake-data">Generation of fake data +</h2><p>To test the contact section of the site, I wanted to populate it with +fake data. <a class="link" href="https://github.com/joke2k/faker" target="_blank" rel="noopener" +>Fake-Factory</a> is an amazing +library that can generate fake data of almost any kind: emails, +association names, acronyms&hellip; It even lets you localise the results +(get Spanish names, for instance) and generate factories for certain +classes (à la Django).</p> +<p>But I also wanted pictures, enter <a class="link" href="http://lorempixel.com/" target="_blank" rel="noopener" +>Lorem Pixel</a>. +With its API you can generate pictures of almost any size, for different +topics (e.g. nightlife, people) and with a custom text. You can even use +an index, so it will always show the same picture.</p> +<p>For instance, the picture below is served through Lorem Pixel.</p> +<p><img src="http://lorempixel.com/400/200/nightlife/" +loading="lazy" +></p> +<p>By the way, if you only want cat pictures, take a look at +<a class="link" href="http://placekitten.com/" target="_blank" rel="noopener" +>Placekitten</a>. And for NSFW text, there&rsquo;s the +<a class="link" href="http://slipsum.com/" target="_blank" rel="noopener" +>Samuel L. Jackson Ipsum</a></p> \ No newline at end of file diff --git a/tags/heroku/page/1/index.html b/tags/heroku/page/1/index.html new file mode 100644 index 0000000..eb35a19 --- /dev/null +++ b/tags/heroku/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/heroku/ + \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 0000000..aa4b9f1 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,36 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/index.xml b/tags/index.xml new file mode 100644 index 0000000..8b19f53 --- /dev/null +++ b/tags/index.xml @@ -0,0 +1 @@ +Tags on J. Fernando Sánchezhttps://balkian.com/tags/Recent content in Tags on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 07 Mar 2025 10:24:52 +0100Semantic Webhttps://balkian.com/tags/semantic-web/Fri, 07 Mar 2025 10:24:52 +0100https://balkian.com/tags/semantic-web/Managementhttps://balkian.com/tags/management/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/tags/management/Teamhttps://balkian.com/tags/team/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/tags/team/Json-Ldhttps://balkian.com/tags/json-ld/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/tags/json-ld/Pydantichttps://balkian.com/tags/pydantic/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/tags/pydantic/Pythonhttps://balkian.com/tags/python/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/tags/python/Rdfhttps://balkian.com/tags/rdf/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/tags/rdf/Nixhttps://balkian.com/tags/nix/Mon, 13 Nov 2023 18:21:46 +0100https://balkian.com/tags/nix/Keyboardhttps://balkian.com/tags/keyboard/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/tags/keyboard/Layouthttps://balkian.com/tags/layout/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/tags/layout/Linuxhttps://balkian.com/tags/linux/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/tags/linux/Logitechhttps://balkian.com/tags/logitech/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/tags/logitech/Rusthttps://balkian.com/tags/rust/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/tags/rust/Mousehttps://balkian.com/tags/mouse/Sat, 30 Oct 2021 00:00:01 +0000https://balkian.com/tags/mouse/Confighttps://balkian.com/tags/config/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/tags/config/Surface Gohttps://balkian.com/tags/surface-go/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/tags/surface-go/Iothttps://balkian.com/tags/iot/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/tags/iot/Mqtthttps://balkian.com/tags/mqtt/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/tags/mqtt/Zigbeehttps://balkian.com/tags/zigbee/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/tags/zigbee/Dotfileshttps://balkian.com/tags/dotfiles/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/tags/dotfiles/Githttps://balkian.com/tags/git/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/tags/git/Githubhttps://balkian.com/tags/github/Fri, 10 Apr 2015 17:47:00 +0000https://balkian.com/tags/github/Apachehttps://balkian.com/tags/apache/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/tags/apache/Nginxhttps://balkian.com/tags/nginx/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/tags/nginx/Webdavhttps://balkian.com/tags/webdav/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/tags/webdav/Zoterohttps://balkian.com/tags/zotero/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/tags/zotero/Gunicornhttps://balkian.com/tags/gunicorn/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/tags/gunicorn/Proxyhttps://balkian.com/tags/proxy/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/tags/proxy/Uwsgihttps://balkian.com/tags/uwsgi/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/tags/uwsgi/Pypihttps://balkian.com/tags/pypi/Sat, 27 Sep 2014 10:00:00 +0000https://balkian.com/tags/pypi/Herokuhttps://balkian.com/tags/heroku/Thu, 27 Mar 2014 14:00:00 +0000https://balkian.com/tags/heroku/Javascripthttps://balkian.com/tags/javascript/Thu, 27 Mar 2014 14:00:00 +0000https://balkian.com/tags/javascript/Rubyhttps://balkian.com/tags/ruby/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/tags/ruby/Startershttps://balkian.com/tags/starters/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/tags/starters/Archhttps://balkian.com/tags/arch/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/arch/Emacshttps://balkian.com/tags/emacs/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/emacs/Lisphttps://balkian.com/tags/lisp/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/lisp/Orghttps://balkian.com/tags/org/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/org/Productivityhttps://balkian.com/tags/productivity/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/productivity/Programminghttps://balkian.com/tags/programming/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/programming/Rpihttps://balkian.com/tags/rpi/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/rpi/Snippethttps://balkian.com/tags/snippet/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/tags/snippet/ \ No newline at end of file diff --git a/tags/iot/index.html b/tags/iot/index.html new file mode 100644 index 0000000..ae39159 --- /dev/null +++ b/tags/iot/index.html @@ -0,0 +1,33 @@ +Tag: Iot - J. Fernando Sánchez +

Tags

1 page

Iot

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/iot/index.xml b/tags/iot/index.xml new file mode 100644 index 0000000..488edba --- /dev/null +++ b/tags/iot/index.xml @@ -0,0 +1,132 @@ +Iot on J. Fernando Sánchezhttps://balkian.com/tags/iot/Recent content in Iot on J. Fernando SánchezHugo -- gohugo.ioen-usSun, 06 Jan 2019 10:00:00 +0000Controlling Zigbee devices with MQTThttps://balkian.com/p/controlling-zigbee-devices-with-mqtt/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/p/controlling-zigbee-devices-with-mqtt/<p>This is a short tutorial on connecting a zigbee device (an Aqara cube) +to an MQTT server, so you can control your zigbee devices from the +network.</p> +<p>If you&rsquo;re anything like me, you&rsquo;re probably a sucker for IoT devices. +For a long time, I&rsquo;ve been using WiFi-enabled lights, and Amazon dash +buttons to control them. To keep these (cheap Chinese) internet enabled +devices away from your network and their respective cloud services, +you&rsquo;ll probably want to set up a dedicated network in your router (more +on this on a future post, maybe). Another disadvantage of WiFi devices +is that they&rsquo;re relatively power hungry.</p> +<p>A popular alternative is using ZigBee for communication. It is a +dedicated protocol similar to bluetooth (BLE), with lower power +requirements and bitrate.</p> +<p>Take the (super cute) aqara cube as an example. It is a small cube that +detects rotation on all of its axes, and tapping events. Here&rsquo;s a +video:</p> +<div class="video-wrapper"> +<iframe loading="lazy" +src="https://www.youtube.com/embed/5YtqG1wEnng" +allowfullscreen +title="YouTube Video" +> +</iframe> +</div> +<p>To connect to zigbee devices you will need a zigbee enabled gateway +(a.k.a. hub), which connects to your WiFi network and your zigbee +devices. Once again, this means adding an internet-enabled device to +your home, and probably a couple of cloud services.</p> +<p>As an alternative, you can set up your own zigbee gateway, and control +it to your home automation platform of choice (e.g. home assistant). We +will cover how to set up a zigbee2mqtt gateway that is also connected to +an MQTT server, so you can use MQTT to control your devices and get +notifications.</p> +<p>What you need:</p> +<ul> +<li><a class="link" href="https://www.aliexpress.com/item/Original-Xiaomi-Mi-Aqara-Cube-Smart-Home-Controller-6-Action-Operation-Fr-Home-Device-Zigbee-Version/32892947622.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>Aqara +cube</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/Wireless-Zigbee-CC2531-CC2540-Zigbee-Sniffer-Bluetooth-BLE-4-0-Dongle-Capture-Module-USB-Programmer-Downloader/32907587711.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC2531 zigbee +sniffer</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/CFSUNBIRD-CC-DEBUGGER-Debugger-and-Programmer-for-RF-System-on-Chips-TI-ORIGINAL-Fast-hipping/32813122315.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC-debugger</a>.</li> +</ul> +<p>You will need to flash your sniffer. For that, you only need to follow +the instructions from the <a class="link" href="https://koenkk.github.io/zigbee2mqtt/" target="_blank" rel="noopener" +>zigbee2mqtt +documentation</a>.</p> +<p>Once you&rsquo;re done flashing, you&rsquo;re ready to set up the zigbee2mqtt +server. For convenience, I wrote a simple docker-compose to deploy a +zigbee2mqtt server and a test mosquitto server:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;2.1&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">zigbee2mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">koenkk/zigbee2mqtt</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">zigbee2mqtt </span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./z2m-data/:/app/data/</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">devices</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="s2">&#34;/dev/ttyACM0&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">eclipse-mosquitto</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">1883</span><span class="p">:</span><span class="m">1883</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">9001</span><span class="p">:</span><span class="m">9001</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./mosquitto.conf:/mosquitto/config/mosquitto.conf</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hass</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">overlay</span><span class="w"> +</span></span></span></code></pre></td></tr></table> +</div> +</div><p>You can test your installation with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">❯ mosquitto_sub -h localhost -p <span class="m">1883</span> -t <span class="s1">&#39;zigbee2mqtt/#&#39;</span> +</span></span><span class="line"><span class="cl">online +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:149,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;rotate_right&#34;</span>,<span class="s2">&#34;angle&#34;</span>:12.8<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;slide&#34;</span>,<span class="s2">&#34;side&#34;</span>:2<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:120<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;wakeup&#34;</span><span class="o">}</span></span></span></code></pre></td></tr></table> +</div> +</div> +<p>zigbee2mqtt supports the following events for the aqara cube: shake, +wakeup, fall, tap, slide, flip180, flip90, rotate_left and +rotate_right. Every event has additional information, such as the sides +involved, or the degrees turned.</p> +<p>Now you are ready to set up home assistant support in zigbee2mqtt +following <a class="link" href="https://koenkk.github.io/zigbee2mqtt/integration/home_assistant.html" target="_blank" rel="noopener" +>this +guide</a>.</p> \ No newline at end of file diff --git a/tags/iot/page/1/index.html b/tags/iot/page/1/index.html new file mode 100644 index 0000000..05939df --- /dev/null +++ b/tags/iot/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/iot/ + \ No newline at end of file diff --git a/tags/javascript/index.html b/tags/javascript/index.html new file mode 100644 index 0000000..26efef5 --- /dev/null +++ b/tags/javascript/index.html @@ -0,0 +1,33 @@ +Tag: Javascript - J. Fernando Sánchez +

Tags

2 pages

Javascript

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/javascript/index.xml b/tags/javascript/index.xml new file mode 100644 index 0000000..0257af5 --- /dev/null +++ b/tags/javascript/index.xml @@ -0,0 +1,182 @@ +Javascript on J. Fernando Sánchezhttps://balkian.com/tags/javascript/Recent content in Javascript on J. Fernando SánchezHugo -- gohugo.ioen-usThu, 27 Mar 2014 14:00:00 +0000Updating EuroLoveMaphttps://balkian.com/p/updating-eurolovemap/Thu, 27 Mar 2014 14:00:00 +0000https://balkian.com/p/updating-eurolovemap/<p>As part of the <a class="link" href="http://www.opener-project.org/2013/07/18/opener-hackathon-in-amsterdam/" target="_blank" rel="noopener" +>OpeNER +hackathon</a> +we decided to build a prototype that would allow us to compare how +different countries feel about several topics. We used the OpeNER +pipeline to get the sentiment from a set of newspaper articles we +gathered from media in several languages. Then we aggregated those +articles by category and country (using the source of the article or the +language it was written in), obtaining the &ldquo;overall feeling&rdquo; of each +country about each topic. Then, we used some fancy JavaScript to make +sense out of the raw information.</p> +<p>It didn&rsquo;t go too bad, it turns out <a class="link" href="http://eurosentiment.eu/wp-content/uploads/2013/07/BOLv9qnCIAAJEek.jpg" target="_blank" rel="noopener" +>we +won</a>.</p> +<p>Now, it was time for a face-lift. I used this opportunity to play with +new technologies and improve it:</p> +<ul> +<li>Using Flask, this time using python 3.3 and Bootstrap 3.0</li> +<li>Cool HTML5+JS cards (thanks to +<a class="link" href="http://pastetophone.com" target="_blank" rel="noopener" +>pastetophone</a>)</li> +<li>Automatic generation of fake personal data to test the interface</li> +<li>Obfuscation of personal emails</li> +</ul> +<p>The result can be <a class="link" href="http://eurolovemap.herokuapp.com/" target="_blank" rel="noopener" +>seen here</a>.</p> +<h2 id="publishing-a-python-3-app-on-heroku">Publishing a Python 3 app on Heroku +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkvirtualenv -p /usr/bin/python3.3 eurolovemap +</span></span></code></pre></td></tr></table> +</div> +</div><p>Since Heroku uses python 2.7 by default, we have to tell it which +version we want, although it supports python 3.4 as well. I couldn&rsquo;t +get python 3.4 working using the +<a class="link" href="https://launchpad.net/~fkrull/&#43;archive/deadsnakes" target="_blank" rel="noopener" +>deadsnakes</a> ppa, so +I used python 3.3 instead, which works fine but is not officially +supported. Just create a file named <em>runtime.txt</em> in your project root, +with the python version you want to use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python-3.3.1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Don&rsquo;t forget to freeze your dependencies so Heroku can install them: +<code>bash pip freze &gt; requirements.txt</code></p> +<h2 id="publishing-personal-emails">Publishing personal emails +</h2><p>There are really sophisticated and effective ways to obfuscate personal +emails so that spammers cannot easily grab yours. However, this time I +needed something really simple to hide our emails from the simplest form +of crawlers. Most of the team are in academia somehow, so in the end all +our emails are available in sites like Google Scholar. Anyway, nobody +likes getting spammed so I settled for a custom <a class="link" href="http://en.wikipedia.org/wiki/Caesar_cipher" target="_blank" rel="noopener" +>Caesar +cipher</a>. Please, don&rsquo;t use +it for any serious application if you are concerned about being spammed.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">blur_email</span><span class="p">(</span><span class="n">email</span><span class="p">):</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">email</span><span class="p">])</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>And this is the client side:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">&#39;profile-email&#39;</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">e</span> <span class="k">in</span> <span class="nx">elems</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">blur</span> <span class="o">=</span> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">email</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">s</span> <span class="k">in</span> <span class="nx">blur</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">blur</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="o">+</span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="nx">a</span><span class="o">-</span><span class="mi">5</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Unfortunately, this approach does not hide your email from anyone using +<a class="link" href="http://phantomjs.org/" target="_blank" rel="noopener" +>PhantomJS</a>, +<a class="link" href="http://zombie.labnotes.org/" target="_blank" rel="noopener" +>ZombieJS</a> or similar. For that, other +approaches like generating a picture with the address would be +necessary. Nevertheless, it is overkill for a really simple ad-hoc +application with custom formatting and just a bunch of emails that would +easily be grabbed manually.</p> +<h2 id="generation-of-fake-data">Generation of fake data +</h2><p>To test the contact section of the site, I wanted to populate it with +fake data. <a class="link" href="https://github.com/joke2k/faker" target="_blank" rel="noopener" +>Fake-Factory</a> is an amazing +library that can generate fake data of almost any kind: emails, +association names, acronyms&hellip; It even lets you localise the results +(get Spanish names, for instance) and generate factories for certain +classes (à la Django).</p> +<p>But I also wanted pictures, enter <a class="link" href="http://lorempixel.com/" target="_blank" rel="noopener" +>Lorem Pixel</a>. +With its API you can generate pictures of almost any size, for different +topics (e.g. nightlife, people) and with a custom text. You can even use +an index, so it will always show the same picture.</p> +<p>For instance, the picture below is served through Lorem Pixel.</p> +<p><img src="http://lorempixel.com/400/200/nightlife/" +loading="lazy" +></p> +<p>By the way, if you only want cat pictures, take a look at +<a class="link" href="http://placekitten.com/" target="_blank" rel="noopener" +>Placekitten</a>. And for NSFW text, there&rsquo;s the +<a class="link" href="http://slipsum.com/" target="_blank" rel="noopener" +>Samuel L. Jackson Ipsum</a></p>Creating my webhttps://balkian.com/p/creating-my-web/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/p/creating-my-web/<p>I&rsquo;ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +<a class="link" href="http://jekyllrb.com" target="_blank" rel="noopener" +>Jekyll</a> will automatically generate static pages +for my posts every time I commit anything new to this repository.</p> +<p>But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.</p> +<p>I hadn&rsquo;t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven&rsquo;t included +any Javascript code in the page. Probably I will use some to add my +<a class="link" href="http://gist.github.com/balkian" target="_blank" rel="noopener" +>gists</a> and +<a class="link" href="http://github.com/balkian" target="_blank" rel="noopener" +>repositories</a>, but we will see about that.</p> +<p>I think the code speaks for itself, so you can check out <a class="link" href="http://github.com/balkian/balkian.github.com" target="_blank" rel="noopener" +>my repository +on Github</a>. You can clone +and deploy it easily like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone +</span></span><span class="line"><span class="cl">https://github.com/balkian/balkian.github.com <span class="nb">cd</span> balkian.github.com +</span></span><span class="line"><span class="cl">jekyll serve -w +</span></span></code></pre></td></tr></table> +</div> +</div><p>I will keep updating this post with information about:</p> +<ul> +<li>Some Jekyll plugins that might be useful</li> +<li>What CSS tricks I learnt</li> +<li>The webfonts I used</li> +<li>The badge on the left side of the page</li> +</ul> \ No newline at end of file diff --git a/tags/javascript/page/1/index.html b/tags/javascript/page/1/index.html new file mode 100644 index 0000000..f6e377a --- /dev/null +++ b/tags/javascript/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/javascript/ + \ No newline at end of file diff --git a/tags/json-ld/index.html b/tags/json-ld/index.html new file mode 100644 index 0000000..7e07477 --- /dev/null +++ b/tags/json-ld/index.html @@ -0,0 +1,33 @@ +Tag: Json-Ld - J. Fernando Sánchez +

Tags

1 page

Json-Ld

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/json-ld/index.xml b/tags/json-ld/index.xml new file mode 100644 index 0000000..874a30e --- /dev/null +++ b/tags/json-ld/index.xml @@ -0,0 +1,96 @@ +Json-Ld on J. Fernando Sánchezhttps://balkian.com/tags/json-ld/Recent content in Json-Ld on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 26 Feb 2025 23:22:59 +0100Bridging RDF, JSON-LD and Dataclasseshttps://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/<p>In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.</p> +<h2 id="the-rdflib-way">The <code>rdflib</code> way +</h2><p><code>rdflib</code> only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, <code>Entity</code>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">entry</span> <span class="o">=</span> <span class="n">Entry</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">entry</span><span class="p">[</span><span class="s1">&#39;vocab:property&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">jsonld</span><span class="p">())</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Would print something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@id&#34;</span><span class="p">:</span> <span class="s2">&#34;:Entry_202505....&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;prefix:Entity&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;vocab:property&#34;</span><span class="p">:</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the</p> +<p>Pros:</p> +<ul> +<li>Flexible/extensible</li> +<li>Lightweight. This is mostly JSON-LD in Python&rsquo;s clothing.</li> +<li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li> +</ul> +<p>Cons:</p> +<ul> +<li>Discoverability. Documentation and examples are needed to know which attributes to use</li> +<li>Error-prone. It is easy to misuse a property, or introduce typos</li> +<li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li> +</ul> +<h2 id="the-object-oriented-way">The object-oriented way +</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like <a class="link" href="https://pydantic.dev/" target="_blank" rel="noopener" +>pydantic</a> that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p> +<p>This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p> +<p>Pros:</p> +<ul> +<li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li> +<li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li> +</ul> +<p>Cons:</p> +<ul> +<li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li> +<li>Polymorphism.</li> +</ul> +<h2 id="a-hybrid-approach">A hybrid approach +</h2><p>Whichever solution is chosen in the end, it needs to:</p> +<ul> +<li>Make it easy and error-proof to add the most common types of annotations</li> +<li>Allow for additional annotations/attributes to be added</li> +<li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li> +<li>Allow for deserialization of custom types</li> +<li>Allow multiple consumers to add their own annotations</li> +</ul> \ No newline at end of file diff --git a/tags/json-ld/page/1/index.html b/tags/json-ld/page/1/index.html new file mode 100644 index 0000000..fee96d4 --- /dev/null +++ b/tags/json-ld/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/json-ld/ + \ No newline at end of file diff --git a/tags/keyboard/index.html b/tags/keyboard/index.html new file mode 100644 index 0000000..7be67b3 --- /dev/null +++ b/tags/keyboard/index.html @@ -0,0 +1,33 @@ +Tag: Keyboard - J. Fernando Sánchez +

Tags

3 pages

Keyboard

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/keyboard/index.xml b/tags/keyboard/index.xml new file mode 100644 index 0000000..30b4f05 --- /dev/null +++ b/tags/keyboard/index.xml @@ -0,0 +1,283 @@ +Keyboard on J. Fernando Sánchezhttps://balkian.com/tags/keyboard/Recent content in Keyboard on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 20 Jan 2023 18:11:00 +0000Kanata: advanced keyboard configurationhttps://balkian.com/p/kanata-advanced-keyboard-configuration/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/p/kanata-advanced-keyboard-configuration/<p><a class="link" href="https://github.com/jtroo/kanata/" target="_blank" rel="noopener" +>Kanata</a> is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you&rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.</p> +<p>The project was inspired by the more popular <a class="link" href="https://github.com/kmonad/kmonad" target="_blank" rel="noopener" +>KMonad</a>, and the author cites some of the <a class="link" href="https://github.com/jtroo/kanata/blob/main/docs/kmonad_comparison.md" target="_blank" rel="noopener" +>differences</a>. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +<a class="link" href="https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd" target="_blank" rel="noopener" +>Here&rsquo;s a very complete config that serves as documentation</a>.</p> +<p>One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.</p> +<p><a class="link" href="https://github.com/rvaiya/keyd/" target="_blank" rel="noopener" +>Keyd</a> is another alternative with a more declarative configuration format, which might lend itself to smaller.</p> +<p>For now I&rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren&rsquo;t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that&rsquo;s the idea. +We&rsquo;ll see if I like it enough to stick with it.</p> +<p>For now, here&rsquo;s my very simple config:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span><span class="lnt">41 +</span><span class="lnt">42 +</span><span class="lnt">43 +</span><span class="lnt">44 +</span><span class="lnt">45 +</span><span class="lnt">46 +</span><span class="lnt">47 +</span><span class="lnt">48 +</span><span class="lnt">49 +</span><span class="lnt">50 +</span><span class="lnt">51 +</span><span class="lnt">52 +</span><span class="lnt">53 +</span><span class="lnt">54 +</span><span class="lnt">55 +</span><span class="lnt">56 +</span><span class="lnt">57 +</span><span class="lnt">58 +</span><span class="lnt">59 +</span><span class="lnt">60 +</span><span class="lnt">61 +</span><span class="lnt">62 +</span><span class="lnt">63 +</span><span class="lnt">64 +</span><span class="lnt">65 +</span><span class="lnt">66 +</span><span class="lnt">67 +</span><span class="lnt">68 +</span><span class="lnt">69 +</span><span class="lnt">70 +</span><span class="lnt">71 +</span><span class="lnt">72 +</span><span class="lnt">73 +</span><span class="lnt">74 +</span><span class="lnt">75 +</span><span class="lnt">76 +</span><span class="lnt">77 +</span><span class="lnt">78 +</span><span class="lnt">79 +</span><span class="lnt">80 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defcfg</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Your keyboard device will likely differ from this.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">linux-dev</span> <span class="nv">/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Windows doesn&#39;t need any input/output configuration entries; however, there</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; must still be a defcfg entry. You can keep the linux-dev entry or delete</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; it and leave it empty.</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsrc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="nf">-</span> <span class="nf">=</span> <span class="nv">bspc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">e</span> <span class="nv">r</span> <span class="no">t</span> <span class="nv">y</span> <span class="nv">u</span> <span class="nv">i</span> <span class="nv">o</span> <span class="nv">p</span> <span class="nv">[</span> <span class="nv">]</span> +</span></span><span class="line"><span class="cl"> <span class="nv">caps</span> <span class="nv">a</span> <span class="nv">s</span> <span class="nv">d</span> <span class="nv">f</span> <span class="nv">g</span> <span class="nv">h</span> <span class="nv">j</span> <span class="nv">k</span> <span class="nv">l</span> <span class="c1">; &#39; ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">\ z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">v</span> <span class="nv">b</span> <span class="nv">n</span> <span class="nv">m</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctl</span> <span class="nv">lmet</span> <span class="nv">lalt</span> <span class="nv">spc</span> <span class="nv">ralt</span> <span class="nv">rmet</span> <span class="nv">rctl</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">qwerty</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@warrows</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@slsft</span> <span class="nv">@dlalt</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@jrmet</span> <span class="nv">@kralt</span> <span class="nv">@lrsft</span> <span class="nv">@</span><span class="c1">;rctrl _ _</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">arrows</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">left</span> <span class="nv">down</span> <span class="nv">up</span> <span class="nv">rght</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">colemak</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">f</span> <span class="nv">p</span> <span class="nv">b</span> <span class="nv">j</span> <span class="nv">l</span> <span class="nv">u</span> <span class="nv">y</span> <span class="c1">; [ ] </span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@rlsft</span> <span class="nv">@slalt</span> <span class="nv">@tlmet</span> <span class="nv">g</span> <span class="nv">m</span> <span class="nv">@nrmet</span> <span class="nv">@eralt</span> <span class="nv">@irsft</span> <span class="nv">@orctrl</span> <span class="o">&#39;</span> <span class="nv">ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">XX</span> <span class="nv">z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">d</span> <span class="nv">v</span> <span class="nv">k</span> <span class="nv">h</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">@smartspace</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">magic</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">@clmk</span> <span class="nv">@qwerty</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">tab</span> <span class="nv">A-tab</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">bspc</span> <span class="nv">esc</span> <span class="nv">_</span> <span class="nv">ret</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defalias</span> +</span></span><span class="line"><span class="cl"> <span class="nv">warrows</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">w</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">arrows</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">alctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">a</span> <span class="nv">lctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">dlalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">d</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">flmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">f</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">jrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">j</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">k</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lrsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">l</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;rctrl (tap-hold 200 200 ; rctrl)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">rlsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">r</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tlmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="no">t</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">nrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">n</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">eralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">e</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">irsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">i</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">orctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">o</span> <span class="nv">rctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">clmk</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">colemak</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">qwerty</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">qwerty</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">smartspace</span> <span class="p">(</span><span class="nv">tap-dance</span> <span class="mi">200</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="nv">spc</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">magic</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="p">(</span><span class="nv">one-shot</span> <span class="mi">300</span> <span class="nv">lalt</span><span class="p">)</span> <span class="nv">spc</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">a</span> +</span></span><span class="line"><span class="cl"> <span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MB850 combi in linuxhttps://balkian.com/p/logitech-mb850-combi-in-linux/Sat, 30 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mb850-combi-in-linux/<p>As a follow-up to my last post, I&rsquo;ve decided to also configure my mk850 combo (k850 + m720 triathlon).</p> +<p>Some notes:</p> +<ul> +<li>The keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc)</li> +<li>The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I&rsquo;ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM.</li> +</ul> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">evdev:input:* </span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_70039</span><span class="o">=</span><span class="s">leftctrl # bind capslock to w </span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0005v046DpB015*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e0</span><span class="o">=</span><span class="s">f19 +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e2=unknown +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7002b=unknown</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the settings have been applied by running <code>evemu-describe</code>:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> sudo /sbin/evemu-describe /dev/input/event&lt;id of your device&gt; <span class="p">|</span> grep KEY_ +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MX Keys for Mac on Linuxhttps://balkian.com/p/logitech-mx-keys-for-mac-on-linux/Fri, 29 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mx-keys-for-mac-on-linux/<p>I recently got Logitech MX Keys for Mac keyboard at work. +The German version, to be more precise. +This version was three times cheaper than the Windows equivalent with either US or ES layout. +Since I touch type anyway, I thought it was a bargain.</p> +<p>As soon as I plugged it in, I realized there were some glaring issues with the keyboard. +First of all, the Meta/Super and Alt keys are reversed in this keyboard. +In the normal/full version of this keyboard, Logitech gives an option to choose between Mac, Windows and iOS host, and that changes the behavior of the keys. +In this version, tho, only iOS and Mac are available.</p> +<p>Besides that, there&rsquo;s the issue of the grave (tilde) and angle keys switched as well.</p> +<p>Switching these keys around would be very easy with Xorg, but Wayland once again complicates things&hellip;</p> +<p>These issues almost made me return the keyboard. +Luckily, tho, there is another option: configuring the keys one level lower than wayland (and X11), through hwdb.</p> +<p>Long story short, this will configure any Logitech keyboard with the same product id (0x4092) to use a saner configuration:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="c1">#File: /etc/udev/hwdb.d/90-logitech-keyboard.hwdb</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0003v046Dp4092*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e2</span><span class="o">=</span><span class="s">leftmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e3=leftalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70039=leftctrl +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70064=102nd +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70035=grave +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e7=rightalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e6=rightmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7006d=compose</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/keyboard/page/1/index.html b/tags/keyboard/page/1/index.html new file mode 100644 index 0000000..142b133 --- /dev/null +++ b/tags/keyboard/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/keyboard/ + \ No newline at end of file diff --git a/tags/layout/index.html b/tags/layout/index.html new file mode 100644 index 0000000..1e36417 --- /dev/null +++ b/tags/layout/index.html @@ -0,0 +1,33 @@ +Tag: Layout - J. Fernando Sánchez +

Tags

1 page

Layout

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/layout/index.xml b/tags/layout/index.xml new file mode 100644 index 0000000..1507917 --- /dev/null +++ b/tags/layout/index.xml @@ -0,0 +1,189 @@ +Layout on J. Fernando Sánchezhttps://balkian.com/tags/layout/Recent content in Layout on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 20 Jan 2023 18:11:00 +0000Kanata: advanced keyboard configurationhttps://balkian.com/p/kanata-advanced-keyboard-configuration/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/p/kanata-advanced-keyboard-configuration/<p><a class="link" href="https://github.com/jtroo/kanata/" target="_blank" rel="noopener" +>Kanata</a> is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you&rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.</p> +<p>The project was inspired by the more popular <a class="link" href="https://github.com/kmonad/kmonad" target="_blank" rel="noopener" +>KMonad</a>, and the author cites some of the <a class="link" href="https://github.com/jtroo/kanata/blob/main/docs/kmonad_comparison.md" target="_blank" rel="noopener" +>differences</a>. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +<a class="link" href="https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd" target="_blank" rel="noopener" +>Here&rsquo;s a very complete config that serves as documentation</a>.</p> +<p>One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.</p> +<p><a class="link" href="https://github.com/rvaiya/keyd/" target="_blank" rel="noopener" +>Keyd</a> is another alternative with a more declarative configuration format, which might lend itself to smaller.</p> +<p>For now I&rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren&rsquo;t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that&rsquo;s the idea. +We&rsquo;ll see if I like it enough to stick with it.</p> +<p>For now, here&rsquo;s my very simple config:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span><span class="lnt">41 +</span><span class="lnt">42 +</span><span class="lnt">43 +</span><span class="lnt">44 +</span><span class="lnt">45 +</span><span class="lnt">46 +</span><span class="lnt">47 +</span><span class="lnt">48 +</span><span class="lnt">49 +</span><span class="lnt">50 +</span><span class="lnt">51 +</span><span class="lnt">52 +</span><span class="lnt">53 +</span><span class="lnt">54 +</span><span class="lnt">55 +</span><span class="lnt">56 +</span><span class="lnt">57 +</span><span class="lnt">58 +</span><span class="lnt">59 +</span><span class="lnt">60 +</span><span class="lnt">61 +</span><span class="lnt">62 +</span><span class="lnt">63 +</span><span class="lnt">64 +</span><span class="lnt">65 +</span><span class="lnt">66 +</span><span class="lnt">67 +</span><span class="lnt">68 +</span><span class="lnt">69 +</span><span class="lnt">70 +</span><span class="lnt">71 +</span><span class="lnt">72 +</span><span class="lnt">73 +</span><span class="lnt">74 +</span><span class="lnt">75 +</span><span class="lnt">76 +</span><span class="lnt">77 +</span><span class="lnt">78 +</span><span class="lnt">79 +</span><span class="lnt">80 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defcfg</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Your keyboard device will likely differ from this.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">linux-dev</span> <span class="nv">/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Windows doesn&#39;t need any input/output configuration entries; however, there</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; must still be a defcfg entry. You can keep the linux-dev entry or delete</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; it and leave it empty.</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsrc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="nf">-</span> <span class="nf">=</span> <span class="nv">bspc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">e</span> <span class="nv">r</span> <span class="no">t</span> <span class="nv">y</span> <span class="nv">u</span> <span class="nv">i</span> <span class="nv">o</span> <span class="nv">p</span> <span class="nv">[</span> <span class="nv">]</span> +</span></span><span class="line"><span class="cl"> <span class="nv">caps</span> <span class="nv">a</span> <span class="nv">s</span> <span class="nv">d</span> <span class="nv">f</span> <span class="nv">g</span> <span class="nv">h</span> <span class="nv">j</span> <span class="nv">k</span> <span class="nv">l</span> <span class="c1">; &#39; ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">\ z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">v</span> <span class="nv">b</span> <span class="nv">n</span> <span class="nv">m</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctl</span> <span class="nv">lmet</span> <span class="nv">lalt</span> <span class="nv">spc</span> <span class="nv">ralt</span> <span class="nv">rmet</span> <span class="nv">rctl</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">qwerty</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@warrows</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@slsft</span> <span class="nv">@dlalt</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@jrmet</span> <span class="nv">@kralt</span> <span class="nv">@lrsft</span> <span class="nv">@</span><span class="c1">;rctrl _ _</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">arrows</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">left</span> <span class="nv">down</span> <span class="nv">up</span> <span class="nv">rght</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">colemak</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">f</span> <span class="nv">p</span> <span class="nv">b</span> <span class="nv">j</span> <span class="nv">l</span> <span class="nv">u</span> <span class="nv">y</span> <span class="c1">; [ ] </span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@rlsft</span> <span class="nv">@slalt</span> <span class="nv">@tlmet</span> <span class="nv">g</span> <span class="nv">m</span> <span class="nv">@nrmet</span> <span class="nv">@eralt</span> <span class="nv">@irsft</span> <span class="nv">@orctrl</span> <span class="o">&#39;</span> <span class="nv">ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">XX</span> <span class="nv">z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">d</span> <span class="nv">v</span> <span class="nv">k</span> <span class="nv">h</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">@smartspace</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">magic</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">@clmk</span> <span class="nv">@qwerty</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">tab</span> <span class="nv">A-tab</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">bspc</span> <span class="nv">esc</span> <span class="nv">_</span> <span class="nv">ret</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defalias</span> +</span></span><span class="line"><span class="cl"> <span class="nv">warrows</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">w</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">arrows</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">alctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">a</span> <span class="nv">lctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">dlalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">d</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">flmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">f</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">jrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">j</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">k</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lrsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">l</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;rctrl (tap-hold 200 200 ; rctrl)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">rlsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">r</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tlmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="no">t</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">nrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">n</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">eralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">e</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">irsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">i</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">orctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">o</span> <span class="nv">rctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">clmk</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">colemak</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">qwerty</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">qwerty</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">smartspace</span> <span class="p">(</span><span class="nv">tap-dance</span> <span class="mi">200</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="nv">spc</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">magic</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="p">(</span><span class="nv">one-shot</span> <span class="mi">300</span> <span class="nv">lalt</span><span class="p">)</span> <span class="nv">spc</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">a</span> +</span></span><span class="line"><span class="cl"> <span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/layout/page/1/index.html b/tags/layout/page/1/index.html new file mode 100644 index 0000000..f9e8d0b --- /dev/null +++ b/tags/layout/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/layout/ + \ No newline at end of file diff --git a/tags/linux/index.html b/tags/linux/index.html new file mode 100644 index 0000000..cd85672 --- /dev/null +++ b/tags/linux/index.html @@ -0,0 +1,33 @@ +Tag: Linux - J. Fernando Sánchez +

Tags

5 pages

Linux

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/linux/index.xml b/tags/linux/index.xml new file mode 100644 index 0000000..8e0d6b8 --- /dev/null +++ b/tags/linux/index.xml @@ -0,0 +1,512 @@ +Linux on J. Fernando Sánchezhttps://balkian.com/tags/linux/Recent content in Linux on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 20 Jan 2023 18:11:00 +0000Kanata: advanced keyboard configurationhttps://balkian.com/p/kanata-advanced-keyboard-configuration/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/p/kanata-advanced-keyboard-configuration/<p><a class="link" href="https://github.com/jtroo/kanata/" target="_blank" rel="noopener" +>Kanata</a> is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you&rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.</p> +<p>The project was inspired by the more popular <a class="link" href="https://github.com/kmonad/kmonad" target="_blank" rel="noopener" +>KMonad</a>, and the author cites some of the <a class="link" href="https://github.com/jtroo/kanata/blob/main/docs/kmonad_comparison.md" target="_blank" rel="noopener" +>differences</a>. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +<a class="link" href="https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd" target="_blank" rel="noopener" +>Here&rsquo;s a very complete config that serves as documentation</a>.</p> +<p>One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.</p> +<p><a class="link" href="https://github.com/rvaiya/keyd/" target="_blank" rel="noopener" +>Keyd</a> is another alternative with a more declarative configuration format, which might lend itself to smaller.</p> +<p>For now I&rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren&rsquo;t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that&rsquo;s the idea. +We&rsquo;ll see if I like it enough to stick with it.</p> +<p>For now, here&rsquo;s my very simple config:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span><span class="lnt">41 +</span><span class="lnt">42 +</span><span class="lnt">43 +</span><span class="lnt">44 +</span><span class="lnt">45 +</span><span class="lnt">46 +</span><span class="lnt">47 +</span><span class="lnt">48 +</span><span class="lnt">49 +</span><span class="lnt">50 +</span><span class="lnt">51 +</span><span class="lnt">52 +</span><span class="lnt">53 +</span><span class="lnt">54 +</span><span class="lnt">55 +</span><span class="lnt">56 +</span><span class="lnt">57 +</span><span class="lnt">58 +</span><span class="lnt">59 +</span><span class="lnt">60 +</span><span class="lnt">61 +</span><span class="lnt">62 +</span><span class="lnt">63 +</span><span class="lnt">64 +</span><span class="lnt">65 +</span><span class="lnt">66 +</span><span class="lnt">67 +</span><span class="lnt">68 +</span><span class="lnt">69 +</span><span class="lnt">70 +</span><span class="lnt">71 +</span><span class="lnt">72 +</span><span class="lnt">73 +</span><span class="lnt">74 +</span><span class="lnt">75 +</span><span class="lnt">76 +</span><span class="lnt">77 +</span><span class="lnt">78 +</span><span class="lnt">79 +</span><span class="lnt">80 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defcfg</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Your keyboard device will likely differ from this.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">linux-dev</span> <span class="nv">/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Windows doesn&#39;t need any input/output configuration entries; however, there</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; must still be a defcfg entry. You can keep the linux-dev entry or delete</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; it and leave it empty.</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsrc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="nf">-</span> <span class="nf">=</span> <span class="nv">bspc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">e</span> <span class="nv">r</span> <span class="no">t</span> <span class="nv">y</span> <span class="nv">u</span> <span class="nv">i</span> <span class="nv">o</span> <span class="nv">p</span> <span class="nv">[</span> <span class="nv">]</span> +</span></span><span class="line"><span class="cl"> <span class="nv">caps</span> <span class="nv">a</span> <span class="nv">s</span> <span class="nv">d</span> <span class="nv">f</span> <span class="nv">g</span> <span class="nv">h</span> <span class="nv">j</span> <span class="nv">k</span> <span class="nv">l</span> <span class="c1">; &#39; ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">\ z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">v</span> <span class="nv">b</span> <span class="nv">n</span> <span class="nv">m</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctl</span> <span class="nv">lmet</span> <span class="nv">lalt</span> <span class="nv">spc</span> <span class="nv">ralt</span> <span class="nv">rmet</span> <span class="nv">rctl</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">qwerty</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@warrows</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@slsft</span> <span class="nv">@dlalt</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@jrmet</span> <span class="nv">@kralt</span> <span class="nv">@lrsft</span> <span class="nv">@</span><span class="c1">;rctrl _ _</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">arrows</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">left</span> <span class="nv">down</span> <span class="nv">up</span> <span class="nv">rght</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">colemak</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">f</span> <span class="nv">p</span> <span class="nv">b</span> <span class="nv">j</span> <span class="nv">l</span> <span class="nv">u</span> <span class="nv">y</span> <span class="c1">; [ ] </span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@rlsft</span> <span class="nv">@slalt</span> <span class="nv">@tlmet</span> <span class="nv">g</span> <span class="nv">m</span> <span class="nv">@nrmet</span> <span class="nv">@eralt</span> <span class="nv">@irsft</span> <span class="nv">@orctrl</span> <span class="o">&#39;</span> <span class="nv">ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">XX</span> <span class="nv">z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">d</span> <span class="nv">v</span> <span class="nv">k</span> <span class="nv">h</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">@smartspace</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">magic</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">@clmk</span> <span class="nv">@qwerty</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">tab</span> <span class="nv">A-tab</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">bspc</span> <span class="nv">esc</span> <span class="nv">_</span> <span class="nv">ret</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defalias</span> +</span></span><span class="line"><span class="cl"> <span class="nv">warrows</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">w</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">arrows</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">alctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">a</span> <span class="nv">lctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">dlalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">d</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">flmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">f</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">jrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">j</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">k</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lrsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">l</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;rctrl (tap-hold 200 200 ; rctrl)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">rlsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">r</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tlmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="no">t</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">nrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">n</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">eralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">e</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">irsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">i</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">orctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">o</span> <span class="nv">rctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">clmk</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">colemak</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">qwerty</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">qwerty</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">smartspace</span> <span class="p">(</span><span class="nv">tap-dance</span> <span class="mi">200</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="nv">spc</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">magic</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="p">(</span><span class="nv">one-shot</span> <span class="mi">300</span> <span class="nv">lalt</span><span class="p">)</span> <span class="nv">spc</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">a</span> +</span></span><span class="line"><span class="cl"> <span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MB850 combi in linuxhttps://balkian.com/p/logitech-mb850-combi-in-linux/Sat, 30 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mb850-combi-in-linux/<p>As a follow-up to my last post, I&rsquo;ve decided to also configure my mk850 combo (k850 + m720 triathlon).</p> +<p>Some notes:</p> +<ul> +<li>The keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc)</li> +<li>The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I&rsquo;ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM.</li> +</ul> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">evdev:input:* </span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_70039</span><span class="o">=</span><span class="s">leftctrl # bind capslock to w </span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0005v046DpB015*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e0</span><span class="o">=</span><span class="s">f19 +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e2=unknown +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7002b=unknown</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the settings have been applied by running <code>evemu-describe</code>:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> sudo /sbin/evemu-describe /dev/input/event&lt;id of your device&gt; <span class="p">|</span> grep KEY_ +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MX Keys for Mac on Linuxhttps://balkian.com/p/logitech-mx-keys-for-mac-on-linux/Fri, 29 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mx-keys-for-mac-on-linux/<p>I recently got Logitech MX Keys for Mac keyboard at work. +The German version, to be more precise. +This version was three times cheaper than the Windows equivalent with either US or ES layout. +Since I touch type anyway, I thought it was a bargain.</p> +<p>As soon as I plugged it in, I realized there were some glaring issues with the keyboard. +First of all, the Meta/Super and Alt keys are reversed in this keyboard. +In the normal/full version of this keyboard, Logitech gives an option to choose between Mac, Windows and iOS host, and that changes the behavior of the keys. +In this version, tho, only iOS and Mac are available.</p> +<p>Besides that, there&rsquo;s the issue of the grave (tilde) and angle keys switched as well.</p> +<p>Switching these keys around would be very easy with Xorg, but Wayland once again complicates things&hellip;</p> +<p>These issues almost made me return the keyboard. +Luckily, tho, there is another option: configuring the keys one level lower than wayland (and X11), through hwdb.</p> +<p>Long story short, this will configure any Logitech keyboard with the same product id (0x4092) to use a saner configuration:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="c1">#File: /etc/udev/hwdb.d/90-logitech-keyboard.hwdb</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0003v046Dp4092*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e2</span><span class="o">=</span><span class="s">leftmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e3=leftalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70039=leftctrl +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70064=102nd +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70035=grave +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e7=rightalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e6=rightmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7006d=compose</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div>Linux on the Microsoft Surface Gohttps://balkian.com/p/linux-on-the-microsoft-surface-go/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/p/linux-on-the-microsoft-surface-go/<p>Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. +These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.</p> +<h2 id="installing-the-kernel">Installing the kernel +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --depth <span class="m">1</span> https://github.com/jakeday/linux-surface.git ~/linux-surface +</span></span><span class="line"><span class="cl">cp -a ~/linux-surface /media/&lt;your usb&gt; +</span></span></code></pre></td></tr></table> +</div> +</div><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -a /media/&lt;your usb&gt;/linux-surface ~/ +</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/linux-surface/ +</span></span><span class="line"><span class="cl">sudo sh setup.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="booting-ubuntu-first">Booting ubuntu first +</h2><p>Switch out of Windows S mode.</p> +<p>Boot into the &ldquo;Command Prompt&rdquo;.</p> +<p>From Windows go to &ldquo;change advanced startup options&rdquo; and select &ldquo;restart now&rdquo;.</p> +<p>When it reboots, choose the &ldquo;Troubleshoot&rdquo; option, then choose the &ldquo;Advanced options&rdquo; option, and finally choose the &ldquo;Command Prompt&rdquo; option.</p> +<p>After the device reboots, login to the command prompt and then you should see a terminal with X:\windows\system32&gt;</p> +<p>At the prompt, check your UEFI entries:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /enum firmware +</span></span></code></pre></td></tr></table> +</div> +</div><p>Copy UEFI entry of &ldquo;Windows Boot Manager&rdquo; to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d &ldquo;Ubuntu&rdquo;</p> +<p>Copy the printed GUID number including the braces {} using Ctrl+C</p> +<p>Set file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \EFI\ubuntu\grubx64.efi</p> +<p>Set Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /set <span class="o">{</span>fwbootmgr<span class="o">}</span> displayorder <span class="o">{</span>guid<span class="o">}</span> /addfirst +</span></span></code></pre></td></tr></table> +</div> +</div><p>Check your UEFI entries again: bcdedit /enum firmware You should see something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">Firmware Boot Manager +</span></span><span class="line"><span class="cl">--------------------- +</span></span><span class="line"><span class="cl">identifier <span class="o">{</span>fwbootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl">displayorder <span class="o">{</span>3510232e-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>bootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>2148799b-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a67-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a68-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl">timeout <span class="m">0</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.</p>Linux Cheatsheethttps://balkian.com/cheatsheet/linux/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/linux/<img src="https://balkian.com/img/linux.png" alt="Featured image of post Linux Cheatsheet" /><h2 id="black-screen-and-lightdm-doesnt-unlock">Black screen and LightDM doesn&rsquo;t unlock +</h2><p>Add this to your /etc/lightdm/lightdm.conf file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[LightDM]</span> +</span></span><span class="line"><span class="cl"><span class="na">logind-check-graphical</span><span class="o">=</span><span class="s">true</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>##Edit previous commands</p> +<p><code>fc</code> is a shell builtin to list and edit previous commands in an editor. +In addition to editing a single line (which you can also do with <code>C-x C-e</code>), it also allows you to edit and run several lines at the same time. +You use it like this:</p> +<p>List previous commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10259</span> nvim deploy.sh +</span></span><span class="line"><span class="cl">10260* <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> <span class="nb">cd</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>List commands with date (in zsh)</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -ld +</span></span><span class="line"><span class="cl">10260* 19:38 <span class="nb">cd</span> .. +</span></span><span class="line"><span class="cl">10261* 19:38 nvim content/cheatsheet/linux.md +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 19:40 <span class="nb">fc</span> -l +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can add the date too:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -fld +</span></span><span class="line"><span class="cl"><span class="m">10262</span> 1/10/2019 19:40 <span class="nb">cd</span> +</span></span><span class="line"><span class="cl"><span class="m">10263</span> 1/10/2019 19:40 <span class="nb">fc</span> -l +</span></span><span class="line"><span class="cl"><span class="m">10264</span> 1/10/2019 19:40 <span class="nb">fc</span> -ld +</span></span></code></pre></td></tr></table> +</div> +</div><p>You can edit a range of commands</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> <span class="m">10262</span> <span class="m">10264</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The range can be relative to the current position, so the previous command is equivalent to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">fc</span> -3 -1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>If you save and exit, all commands are executed as a script, and it will be added to your history.</p> +<p>Source: <a class="link" href="https://shapeshed.com/unix-fc/" target="_blank" rel="noopener" +>https://shapeshed.com/unix-fc/</a></p> +<h2 id="prevent-logoff-from-killing-tmux-sessions">Prevent logoff from killing tmux sessions +</h2><p>Lately I&rsquo;ve noticed that logging out of i3, intentionally or when i3 fails, would also kill any tmux or emacs sessions. +This is extremely annoying.</p> +<p>This is caused by a new default in logind (systemd&rsquo;s login) to kill user process on logoff. +You can revert this setting in your logind.conf (<code>/etc/systemd/logind.conf</code>):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">KillUserProcesses</span><span class="o">=</span><span class="s">no</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Or only for a specific process (e.g., tmux):</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemd-run --scope --user tmux +</span></span></code></pre></td></tr></table> +</div> +</div><p>Source: <a class="link" href="https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session" target="_blank" rel="noopener" +>https://unix.stackexchange.com/questions/490267/prevent-logoff-from-killing-tmux-session</a></p> +<h2 id="upload-a-temporary-file">Upload a temporary file +</h2><p>Sometimes you just need to copy/paste a file from a server, and copying from the terminal can be a hassle. +These two services are command-line &ldquo;pastebins&rdquo; just one curl away:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span><span class="lnt">8 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F <span class="s1">&#39;sprunge=&lt;-&#39;</span> http://sprunge.us +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> curl -F <span class="s1">&#39;f:1=&lt;-&#39;</span> ix.io +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># OR</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">&lt;command&gt; <span class="p">|</span> curl -F<span class="s2">&#34;file=@-&#34;</span> https://ttm.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h1 id="install-fortinet-sslvpn-support-for-networkmanager">Install Fortinet SSLVPN support for NetworkManager +</h1><p>UPM (Universidad Politécnica de Madrid) uses a propriatary VPN solution. +The instructions for GNU/Linux on their website involve downloading a specific client (<code>.tar.gz</code>) and manually running it. +That works, but it is kind of a hassle. +A much more convenient alternative is installing this NetworkManager plugin:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pacman -Sy networkmanager-fortisslvpn +</span></span><span class="line"><span class="cl"><span class="c1"># Or apt get install networkmanager-fortisslvpn </span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now you can simply add a new VPN connection in NetworkManager and manage it as you would any other connection.</p> \ No newline at end of file diff --git a/tags/linux/page/1/index.html b/tags/linux/page/1/index.html new file mode 100644 index 0000000..ec22679 --- /dev/null +++ b/tags/linux/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/linux/ + \ No newline at end of file diff --git a/tags/lisp/index.html b/tags/lisp/index.html new file mode 100644 index 0000000..1e9cf29 --- /dev/null +++ b/tags/lisp/index.html @@ -0,0 +1,33 @@ +Tag: Lisp - J. Fernando Sánchez +

Tags

1 page

Lisp

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/lisp/index.xml b/tags/lisp/index.xml new file mode 100644 index 0000000..42a98db --- /dev/null +++ b/tags/lisp/index.xml @@ -0,0 +1,9 @@ +Lisp on J. Fernando Sánchezhttps://balkian.com/tags/lisp/Recent content in Lisp on J. Fernando SánchezHugo -- gohugo.ioen-usEmacs: show plain text versionhttps://balkian.com/p/emacs-show-plain-text-version/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/emacs-show-plain-text-version/<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">font-lock-mode</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/lisp/page/1/index.html b/tags/lisp/page/1/index.html new file mode 100644 index 0000000..c156928 --- /dev/null +++ b/tags/lisp/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/lisp/ + \ No newline at end of file diff --git a/tags/logitech/index.html b/tags/logitech/index.html new file mode 100644 index 0000000..4dc123d --- /dev/null +++ b/tags/logitech/index.html @@ -0,0 +1,33 @@ +Tag: Logitech - J. Fernando Sánchez +

Tags

3 pages

Logitech

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/logitech/index.xml b/tags/logitech/index.xml new file mode 100644 index 0000000..9697aef --- /dev/null +++ b/tags/logitech/index.xml @@ -0,0 +1,283 @@ +Logitech on J. Fernando Sánchezhttps://balkian.com/tags/logitech/Recent content in Logitech on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 20 Jan 2023 18:11:00 +0000Kanata: advanced keyboard configurationhttps://balkian.com/p/kanata-advanced-keyboard-configuration/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/p/kanata-advanced-keyboard-configuration/<p><a class="link" href="https://github.com/jtroo/kanata/" target="_blank" rel="noopener" +>Kanata</a> is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you&rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.</p> +<p>The project was inspired by the more popular <a class="link" href="https://github.com/kmonad/kmonad" target="_blank" rel="noopener" +>KMonad</a>, and the author cites some of the <a class="link" href="https://github.com/jtroo/kanata/blob/main/docs/kmonad_comparison.md" target="_blank" rel="noopener" +>differences</a>. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +<a class="link" href="https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd" target="_blank" rel="noopener" +>Here&rsquo;s a very complete config that serves as documentation</a>.</p> +<p>One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.</p> +<p><a class="link" href="https://github.com/rvaiya/keyd/" target="_blank" rel="noopener" +>Keyd</a> is another alternative with a more declarative configuration format, which might lend itself to smaller.</p> +<p>For now I&rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren&rsquo;t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that&rsquo;s the idea. +We&rsquo;ll see if I like it enough to stick with it.</p> +<p>For now, here&rsquo;s my very simple config:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span><span class="lnt">41 +</span><span class="lnt">42 +</span><span class="lnt">43 +</span><span class="lnt">44 +</span><span class="lnt">45 +</span><span class="lnt">46 +</span><span class="lnt">47 +</span><span class="lnt">48 +</span><span class="lnt">49 +</span><span class="lnt">50 +</span><span class="lnt">51 +</span><span class="lnt">52 +</span><span class="lnt">53 +</span><span class="lnt">54 +</span><span class="lnt">55 +</span><span class="lnt">56 +</span><span class="lnt">57 +</span><span class="lnt">58 +</span><span class="lnt">59 +</span><span class="lnt">60 +</span><span class="lnt">61 +</span><span class="lnt">62 +</span><span class="lnt">63 +</span><span class="lnt">64 +</span><span class="lnt">65 +</span><span class="lnt">66 +</span><span class="lnt">67 +</span><span class="lnt">68 +</span><span class="lnt">69 +</span><span class="lnt">70 +</span><span class="lnt">71 +</span><span class="lnt">72 +</span><span class="lnt">73 +</span><span class="lnt">74 +</span><span class="lnt">75 +</span><span class="lnt">76 +</span><span class="lnt">77 +</span><span class="lnt">78 +</span><span class="lnt">79 +</span><span class="lnt">80 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defcfg</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Your keyboard device will likely differ from this.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">linux-dev</span> <span class="nv">/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Windows doesn&#39;t need any input/output configuration entries; however, there</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; must still be a defcfg entry. You can keep the linux-dev entry or delete</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; it and leave it empty.</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsrc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="nf">-</span> <span class="nf">=</span> <span class="nv">bspc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">e</span> <span class="nv">r</span> <span class="no">t</span> <span class="nv">y</span> <span class="nv">u</span> <span class="nv">i</span> <span class="nv">o</span> <span class="nv">p</span> <span class="nv">[</span> <span class="nv">]</span> +</span></span><span class="line"><span class="cl"> <span class="nv">caps</span> <span class="nv">a</span> <span class="nv">s</span> <span class="nv">d</span> <span class="nv">f</span> <span class="nv">g</span> <span class="nv">h</span> <span class="nv">j</span> <span class="nv">k</span> <span class="nv">l</span> <span class="c1">; &#39; ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">\ z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">v</span> <span class="nv">b</span> <span class="nv">n</span> <span class="nv">m</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctl</span> <span class="nv">lmet</span> <span class="nv">lalt</span> <span class="nv">spc</span> <span class="nv">ralt</span> <span class="nv">rmet</span> <span class="nv">rctl</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">qwerty</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@warrows</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@slsft</span> <span class="nv">@dlalt</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@jrmet</span> <span class="nv">@kralt</span> <span class="nv">@lrsft</span> <span class="nv">@</span><span class="c1">;rctrl _ _</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">arrows</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">left</span> <span class="nv">down</span> <span class="nv">up</span> <span class="nv">rght</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">colemak</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">f</span> <span class="nv">p</span> <span class="nv">b</span> <span class="nv">j</span> <span class="nv">l</span> <span class="nv">u</span> <span class="nv">y</span> <span class="c1">; [ ] </span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@rlsft</span> <span class="nv">@slalt</span> <span class="nv">@tlmet</span> <span class="nv">g</span> <span class="nv">m</span> <span class="nv">@nrmet</span> <span class="nv">@eralt</span> <span class="nv">@irsft</span> <span class="nv">@orctrl</span> <span class="o">&#39;</span> <span class="nv">ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">XX</span> <span class="nv">z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">d</span> <span class="nv">v</span> <span class="nv">k</span> <span class="nv">h</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">@smartspace</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">magic</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">@clmk</span> <span class="nv">@qwerty</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">tab</span> <span class="nv">A-tab</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">bspc</span> <span class="nv">esc</span> <span class="nv">_</span> <span class="nv">ret</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defalias</span> +</span></span><span class="line"><span class="cl"> <span class="nv">warrows</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">w</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">arrows</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">alctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">a</span> <span class="nv">lctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">dlalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">d</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">flmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">f</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">jrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">j</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">k</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lrsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">l</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;rctrl (tap-hold 200 200 ; rctrl)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">rlsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">r</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tlmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="no">t</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">nrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">n</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">eralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">e</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">irsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">i</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">orctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">o</span> <span class="nv">rctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">clmk</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">colemak</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">qwerty</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">qwerty</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">smartspace</span> <span class="p">(</span><span class="nv">tap-dance</span> <span class="mi">200</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="nv">spc</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">magic</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="p">(</span><span class="nv">one-shot</span> <span class="mi">300</span> <span class="nv">lalt</span><span class="p">)</span> <span class="nv">spc</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">a</span> +</span></span><span class="line"><span class="cl"> <span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MB850 combi in linuxhttps://balkian.com/p/logitech-mb850-combi-in-linux/Sat, 30 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mb850-combi-in-linux/<p>As a follow-up to my last post, I&rsquo;ve decided to also configure my mk850 combo (k850 + m720 triathlon).</p> +<p>Some notes:</p> +<ul> +<li>The keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc)</li> +<li>The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I&rsquo;ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM.</li> +</ul> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">evdev:input:* </span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_70039</span><span class="o">=</span><span class="s">leftctrl # bind capslock to w </span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0005v046DpB015*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e0</span><span class="o">=</span><span class="s">f19 +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e2=unknown +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7002b=unknown</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the settings have been applied by running <code>evemu-describe</code>:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> sudo /sbin/evemu-describe /dev/input/event&lt;id of your device&gt; <span class="p">|</span> grep KEY_ +</span></span></code></pre></td></tr></table> +</div> +</div>Logitech MX Keys for Mac on Linuxhttps://balkian.com/p/logitech-mx-keys-for-mac-on-linux/Fri, 29 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mx-keys-for-mac-on-linux/<p>I recently got Logitech MX Keys for Mac keyboard at work. +The German version, to be more precise. +This version was three times cheaper than the Windows equivalent with either US or ES layout. +Since I touch type anyway, I thought it was a bargain.</p> +<p>As soon as I plugged it in, I realized there were some glaring issues with the keyboard. +First of all, the Meta/Super and Alt keys are reversed in this keyboard. +In the normal/full version of this keyboard, Logitech gives an option to choose between Mac, Windows and iOS host, and that changes the behavior of the keys. +In this version, tho, only iOS and Mac are available.</p> +<p>Besides that, there&rsquo;s the issue of the grave (tilde) and angle keys switched as well.</p> +<p>Switching these keys around would be very easy with Xorg, but Wayland once again complicates things&hellip;</p> +<p>These issues almost made me return the keyboard. +Luckily, tho, there is another option: configuring the keys one level lower than wayland (and X11), through hwdb.</p> +<p>Long story short, this will configure any Logitech keyboard with the same product id (0x4092) to use a saner configuration:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="c1">#File: /etc/udev/hwdb.d/90-logitech-keyboard.hwdb</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0003v046Dp4092*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e2</span><span class="o">=</span><span class="s">leftmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e3=leftalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70039=leftctrl +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70064=102nd +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_70035=grave +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e7=rightalt +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e6=rightmeta +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7006d=compose</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/logitech/page/1/index.html b/tags/logitech/page/1/index.html new file mode 100644 index 0000000..2f2d501 --- /dev/null +++ b/tags/logitech/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/logitech/ + \ No newline at end of file diff --git a/tags/management/index.html b/tags/management/index.html new file mode 100644 index 0000000..ae65c3f --- /dev/null +++ b/tags/management/index.html @@ -0,0 +1,33 @@ +Tag: Management - J. Fernando Sánchez +

Tags

1 page

Management

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/management/index.xml b/tags/management/index.xml new file mode 100644 index 0000000..0857b1b --- /dev/null +++ b/tags/management/index.xml @@ -0,0 +1,327 @@ +Management on J. Fernando Sánchezhttps://balkian.com/tags/management/Recent content in Management on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 05 Mar 2025 09:25:54 +0100Tips for efficient collaborationhttps://balkian.com/p/efficient-collaboration/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/p/efficient-collaboration/<h2 id="background">Background +</h2><blockquote class="note"><p>TL;DR I work in academia. This post focuses on advice I&rsquo;d give a younger me to be a more effective supervisor and project lead.</p></blockquote> +<p>My role in my research group has evolved from individual contributor to project lead that manages a team of multiple students. +This often involves coordinatating with other senior researchers and their teams.</p> +<p>This post is a collection of advice I would have given myself back when I started this journey. +It is also an excuse to reflect on these ideas I&rsquo;ve been implicitly applying everyday, and maybe learn a few things more in the process.</p> +<p>In my field, projects are often tied to a specific grant or some sort of public funding. +This means that the main concern of the lead is to ensure that the results at the end of the project match the description in the project proposal. +It also typically means maximizing the number of publications related to the project and their overall impact.</p> +<p>To do so, most projects rely on three types of staff: a) senior researchers (post-doc); b) junior researchers (PhD students); and c) interns doing their bachelor&rsquo;s or master&rsquo;s thesis. +The level of contribution is generally inversely propotional to the level of mastery of the contributor: PhD students design and develop the main contributions (both software and experimental) under the supervision of senior researchers (advisor/supervisor), and interns take care of tasks that are narrow in scope and not crucial to any academic contribution. +For instance, a PhD student may develop a new model for text classification, and an intern will wrap that model in an HTTP service with a nice UI. +When the service and UI part is intricate and has some potential academic merit, that task may be conducted by a PhD student as part of their thesis. +That was precisely the case with Senpy, which was part of my PhD thesis, and it has since been used by dozens of students to develop services in the context of other research projects.</p> +<h2 id="reasons-to-form-a-team">Reasons to form a team +</h2><p>In my opinion, a team has two advantages over a single contributor. +The first one is that collaboration often generates <strong>synergies</strong>, leading to surprising and enriching results (a team is greater than the sum of its parts) +Carefully selecting your team members and creating an environment that is conducing to these synergies is a topic on its own, and I will not get too deep into it here.</p> +<p>The second advantage can be summarized as <strong>concurrency</strong>: tasks can be tackled by more than one member. +This often implies some sort of parallelism. +Tasks tend to be split between different members, in hopes of speeding up the process. +But it can also be beneficial to have concurrency even in the absence of parallelism: different members can take turns solving the same problem. +This is common when the task requires exclusive access to a resource (e.g., performing an experiment on an expensive machine).</p> +<h2 id="challenges-of-teams-of-students">Challenges of teams (of students) +</h2><p>Just like in computer science, coordination in a concurrent project involves a non-negligible overhead. +The objective is to minimize this overhead.</p> +<p>I&rsquo;ve really struggled with managing teams in the past. +Given my context, I often attributed the failures to lack of time (having to juggle teaching and research), lack of training and preparation on the intern&rsquo;s side (they&rsquo;re often undergrads), lack of appeal or definition of the task (too academic-y), or any other external factors.</p> +<p>While all those aspects play an important role, some of them are out of our control as a project lead in academia: grants require a certain type of project and workflow, and the quality of our interns is bounded by the quality of our degrees. +So I think it is more constructive to focus on things that we can control. +In other words: we have to play the hand we&rsquo;re given.</p> +<p>Besides, there is no merit in achieving good results with excellent students/engineers. +They would succeed on their own even if you weren&rsquo;t there. +The real test for a good leader is succeeding with a subpar team.</p> +<p>In that vein, I&rsquo;ve reflected on my mistakes as a leader, and the inefficiencies of the teams around me. +I&rsquo;d classify my failures in the following areas:</p> +<ul> +<li>Delegation (and lack thereof). Piling up too many tasks and blocking progress.</li> +<li>Communication. Not having a coherent view of the state of affairs, the details of specific tasks, the general processes to follow, or the priorities of different tasks within a project.</li> +<li>Direction (or purpose). Not having a common direction</li> +</ul> +<h3 id="delegation">Delegation +</h3><p>I have a tendency to become a bottleneck in any project I am involved in: many tasks end up depending on me, either directly or indirectly. In the concurrency metaphor, I become a lock for many tasks, and a single executor for the rest. +This issue was not that apparent early on, when most of my work was as an individual contributor or I had the bandwidth to supervise and complete my tasks.</p> +<p>I think it is quite common to feel like delegating a task in these scenarios means:</p> +<ol> +<li>Defining the task in advance</li> +<li>Choosing an assignee for the task</li> +<li>Setting a deadline for the task</li> +<li>Explaining the task and the relevant context</li> +<li>Replying to multiple questions by e-mail or in person. Extra points when the questions make you wonder if any part of the explanation was ever clear.</li> +<li>Reviewing the results after the deadline</li> +<li>Realizing the assignee misunderstood the task or delivered something not even close to what you agreed upon</li> +<li>Going back to point 3.</li> +<li>When you&rsquo;re unlucky or short on time: giving up and doing the task yourself</li> +</ol> +<p>Many times, if felt like delegating tasks only lead to frustration and wasted time. +Especially when compared to the alternative:</p> +<ul> +<li>Defining the task</li> +<li>Setting a deadline</li> +<li>Finishing the task</li> +<li>Profit</li> +</ul> +<p>Luckily, some students and projects were an exception to this. +They worked autonomously and delivered something beyond the minimum requirements. +This reinforced my helplessness and the feeling that the problem was not being able to work with experienced engineers.</p> +<p>However, I now believe that the truth lies somewhere in between. +Sometimes your circumstances make it quite hard or inefficient to delegate tasks. +And some times may not be good candidates for delegation. +But most of the times you can take advantage of having an extra pair of hands, you just have to do that effectively.</p> +<h3 id="communication">Communication +</h3><p>Small teams rely on implicit knowledge more than they realize. +Even more so if the team is made up of highly specialized people that have worked in the same environment with mostly the same people for years.</p> +<p>Communication is a broad term. +It includes technical and concrete things such as how a certain task should be done. +But it also includes broader things like etiquette, organizational values, and who is more willing to help you out on certain topics on a Friday afternoon.</p> +<p>Here, I would take a page out of Python&rsquo;s zen and recommend that &ldquo;explicit is better than implicit&rdquo;. +Implicit (or tacit) knowledge comes with a whole set of drawbacks:</p> +<ul> +<li>It makes onboarding new users harder. Without a common knowledge base, all knowledge transference has to rely on personal interactions. Even worse, those interactions are probably organized on the spot, and are likely to miss important points.</li> +<li>It makes you heavily reliant on your current members (and their memory).</li> +<li>It impedes proper evaluationn and progress, since they are not written anywhere.</li> +<li>It increases the likelihood of misunderstandings when two members have conflicting beliefs, and makes it harder to detect them until it is too late.</li> +<li>It makes contradictions and (unknowingly) changing your mind much more likely. It can happen to the best of us, especially if you are involved in too many projects. When contraditions happen often, your colleagues will learn not to rely on your opinion.</li> +</ul> +<p>On the other hand, communication has to go both ways. +This means that your newer members need to be able to communicate when something is going wrong or can be improved (backpressure). +They should also feel free to talk about their motivation, state of mind, and feelings, <strong>when appropriate</strong>. +That last part is quite subjective, of course. +Try to find your - and your organization&rsquo;s - middle ground between &ldquo;I don&rsquo;t care how you feel, just do your job&rdquo; and &ldquo;sure, you can go to the Maldives on short notice. Oh, and don&rsquo;t worry about not having met a deadline in months, I&rsquo;m sure you&rsquo;re stressed and can use some vacation but will work remotely if we need you&rdquo;.</p> +<p>I personally feel a sweet spot is treating your coworkers like people, being empathetic and compassionate. +Part of being a good coworker is fulfilling the duties and obligations you accepted when signing your contract. +First and foremost, because not fulfilling them means someone else will have to work harder to make up for it. +And, secondly, because doing our part is the only way to move the organization (and research) forward.</p> +<h3 id="direction">Direction +</h3><p>By failure in direction I mean not keeping a consistent and shared set of general goals, principles and values in your organization. +In order to really take part in any enterprise, you need to have a clear understanding of the objectives and motivation. +When it comes to specific tasks, <em>what you&rsquo;re doing</em> is often not as important as the <em>why you&rsquo;re doing it</em>. +In fact, there may be times where you aren&rsquo;t truly sure of what exactly it is that you are doing, but you trust the process and the motivation behind the task.</p> +<p>I&rsquo;ve seen two failure modes in this regard. +The first one is to not have a clear direction. +The end result is that members of the team are not really that committed. +If no other <em>why</em> is provided, we are only left with <em>because they pay me to do it</em>. +And academia is not known to pay particularly well, to be honest.</p> +<p>The other mode is to provide contradicting or incompatible directions. +This can be in a short period of time, leading to the impression that there isn&rsquo;t really any conviction in the message. +But it can also be done over a longer period of time. +That can be perfectly acceptable, provided that the change in direction is justified and compatible with the principles of the organization.</p> +<p>Failure in direction is somewhat related to communication, but it is subtly different. +An organization can excel at communication, but change their direction constantly. +Arguably, a thorough communication strategy makes radical changes in direction less likely. +On the one hand, a change in direction needs to be documented, which can be a pain. +On the other hand, a written change is easier to spot and more likely to generate complaints.</p> +<h2 id="rules">Rules +</h2><p>I&rsquo;d argue that the path to successfully managing a research team lies in roughly the following key goals:</p> +<ul> +<li>Fostering autonomy</li> +<li>Avoiding miscommunication</li> +<li>Optimizing your contribution</li> +</ul> +<p>The remaining of the post will be a series of tasks or rules to achieve these goals.</p> +<blockquote class="warning"><p>Most of these ideas probably generalize well to collaboration outside of academia, but I hesitate to make more general claims.</p></blockquote> +<h3 id="fostering-automony">Fostering automony +</h3><p>The tips here are aimed at avoiding supervision overhead and training future leads.</p> +<h4 id="provide-a-simplified-version-of-the-bigger-picture">Provide a (simplified version of the) bigger picture +</h4><p>Try to paint the bigger picture, even for menial tasks within large projects. +For you, this may be the nth project you&rsquo;re involved in this year, but the new intern may not have even heard about European projects before. +Going back to the idea of direction, it is easier to work on something if you know the context of your work.</p> +<p>Having a general idea of the project and the context of your task will also help you make decisions on your own. +For instance, if I am told to develop a shiny new API for text classification, I may have to ask many questions: 1) what will be input look like?; 2) what should the parameters be?; 3) am I using POST or GET requests?; 4) should I return a JSON object or an XML?&hellip; +What if, instead of that, I am also told this API will be used in the context of project X, that our organization will be the only consumers of the API, and they also give me a link to the project&rsquo;s docs. +I may be able to figure out some of those answers on my own (e.g., by finding examples in the project&rsquo;s website), or decide that some questions are not vital at this point (e.g., if we are the only consumers, we can change from GET to POST if we need to much more easily).</p> +<p>One caveat here is that a link to the documentation or some vague words about the project do not constitute proper context. +You are responsible for summarizing the important bits of the context, providing instructions on how to navigate the reference materials, and being open to answer questions that may arise in the exchange.</p> +<h4 id="do-not-discuss-implementation-details-unless-strictly-necessary">Do not discuss implementation details unless strictly necessary +</h4><p>There is a fine line between discussing a non-trivial implementation detail and bikeshedding for hours about class names and code best practices. +For that reason, you should try to prioritize discussing high-level parts of the problem and the assignment, and trust the student to figure out the details on their own, or come back to you for clarification.</p> +<p>It is very common that students focus on very specific details when they are sharing their progress with their supervisors. +They will generally try to start by showing snippets of code and their results. +I find it helpful to remind them to explain their problems top-to-bottom, starting with a sentence or two about the context of their project, the description and motivation of the specific task they were doing, and the relationship with previous (and future) tasks. +That usually helps figure out the level of understanding of the student, whether there are any conceptual errors, and whether the specific block or problem is really worth discussing during the meeting.</p> +<p>Some technical problems will warrant a discussion in detail, either due to their complexity or their importance to the project. +In those cases, always limit the time you will spend on that specific issue ahead of time, and make sure to allow for some time at the end of the meeting to go back to any important high-level details.</p> +<p>If there are other students that worked on similar projects, do not hesitate to refer your new student to them. +It can be an opportunity for them to collaborate, and for the original student to work on explaining and teaching technical issues.</p> +<h4 id="provide-feedback">Provide feedback +</h4><p>Make a point of evaluating the results of each student on every level, and provide constructive and actionable feedback to them. +Even if no technical issues arise during the project, try to review the code and give some tips (e.g., formatting, code structure, DRY). +Try to focus on bigger issues and enforcing best practices before nitpicking and giving feedback on small subjective improvements.</p> +<p>Make it clear when your feedback is objective/best practice (e.g., a function is deprecated) and when it is a matter of preference. +If it is the latter, try to provide more than one alternative, to encourage them to think about it and make an educated decision.</p> +<h4 id="take-documentation-and-knowledge-transfer-seriously">Take documentation and knowledge transfer seriously +</h4><p>Taking the time to write down basic documentation can save a lot of time in the long run. +Besides, most of the job of mentoring a new student is lost when that student finishes their degree and leaves to find a job in industry. +Good documentation can remain in your organization and be extended long after the intern is gone.</p> +<p>This is very obvious for specific tools, whether internal or public. +Good documentation means any new member can check the tool and use it without much assistance. +Even better documentation helps newcomers contribute to the tool. +Make sure to make it clear who to approach if something is missing from the documentation, and make it easier to do so than to make assumptions and use the tool incorrectly.</p> +<p>Writing documentation can be very time consuming, and sometimes it is hard to know exactly what things to focus on when writing the docs. +You need to anticipate the needs of the future user. +If you are short on time, a good strategy is to delegate the writing of the documentation. +Instead of going into details, you can write a very barebones version and training a new user to use and contribute to your tool. +Then, leave it up to the new user to extend the documentation, including more details and pitfalls. +As a bonus, reading and fixing the docs will give you a better sense of how well that new user understands the tool, as well as possible improvements.</p> +<p>This tip also applies to more general areas such as machine learning, graph neural networks, or simulation. +Just remember you do not need to reinvent the wheel in those cases. +A simple summary and a list of references to expand on the topic could be more than enough. +Make sure to also include any specifics that apply to your organization. +For instance, point to repositories on github (public or private) that can be used to explore the topic, examples of similar projects in the domain, etc.</p> +<p>Identify what information is important for any new hire and present it to them as clearly as possible. +Part of that information should be where and how common knowledge is stored and shared, should they need more information in the future. +Make this documentation as easy to discover and consume as possible. +Centralizing this common information in the form of a wiki is often a good idea.</p> +<p>Lastly, make it easy for any member of your organization to update this common documentation, and encourage them to do so. +Whenever a member asks you something useful that is not documented, don&rsquo;t just answer the question. +Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer. +If your organization&rsquo;s culture does not encourage using these docs, they will quickly get outdated and fall out of use.</p> +<p>One example of taking this documentation approach really seriously is <a class="link" href="https://oxide.computer/" target="_blank" rel="noopener" +>Oxide (computer company)</a>. +They have a process they call <a class="link" href="https://rfd.shared.oxide.computer/" target="_blank" rel="noopener" +>Request For Discussion (RFD)</a>, which they use to discuss and document both technical and organizational decisions. +For instance, they have <a class="link" href="https://rfd.shared.oxide.computer/rfd/0537" target="_blank" rel="noopener" +>RFDs on why they record every meeting</a>, <a class="link" href="https://rfd.shared.oxide.computer/rfd/0110" target="_blank" rel="noopener" +>RFDs about their choice of database</a>, and even <a class="link" href="https://rfd.shared.oxide.computer/rfd/0001" target="_blank" rel="noopener" +>an meta-RFD that discusses the motivation RFDs and how the process should work</a>.</p> +<h4 id="trust-your-teammates-ability-to-learn">Trust your teammate&rsquo;s ability to learn +</h4><p>I&rsquo;ve been bitten by this way too many times. +Your students are probably more capable of learning than you think, especially if you have set up your documentation right. +What they lack in experience, they make up for with free time, a (more) neuroplasticity and determination.</p> +<p>Sure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.</p> +<h4 id="use-tools-wisely">Use tools wisely +</h4><p>Your students probably have little experience with code versioning, reviewing processes, time management, etc. +A good choice of tools and some training can go a long way and make your life much easier in the long run. +It will also give your students a taste of what working in a bigger/real company feels like and a head start.</p> +<p>For instance, using git makes it easier to collaborate on code. +It also ensures that your results will not be lost if your student&rsquo;s laptop gets stolen.</p> +<p>Using GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students. +It will force them to commit working code, and it will make it easier to check their results and discuss the end result.</p> +<p>Using overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results. +You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration. +In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.</p> +<p>Also, on a related note, make sure every team member has a proper development setup. +It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work. +It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.</p> +<h4 id="encourage-cooperation">Encourage cooperation +</h4><p>Do not become the center of every conversation. +If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.</p> +<p>The ability to discuss with your peers and report only when needed will be extremely important for them in the future. +They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are). +That might lead to valuable insights and improvements for your team and project.</p> +<p>Moreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.</p> +<h4 id="reward-proactivity">Reward proactivity +</h4><p>The whole point of this section is to get your team to work independently when possible. +Be explicit about this goal to make sure it is clear to everyone. +And encourage behavior that aligns with this goal, even on a small scale.</p> +<p>For instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements. +Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time. +Do not jump straight to criticize it. +Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.</p> +<h4 id="dont-be-a-perfectionist">Don&rsquo;t be a perfectionist +</h4><p>Perfect is the enemy of done. +It is also the enemy of a happy co-worker.</p> +<p>Try to remember that you are dealing with students, and you were probably no better at their age. +Besides, you probably delegated the task beause you did not have any spare time to do it yourself. +<code>FIXME</code> is often better than <code>TODO</code>.</p> +<p>Take the opportunity to provide some feedback and teach them something useful. +Some mistakes are also worth adding to your documentation, or presenting to other students in a presentation.</p> +<h3 id="avoiding-miscommunication">Avoiding miscommunication +</h3><p>A common source of wasted effort and unnecessary back-and-forth is miscommunication. +These are some tips to help keep everyone on the team informed and aligned.</p> +<h4 id="make-priorities-clear">Make priorities clear +</h4><p>All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks. +This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.</p> +<h4 id="define-boundaries-and-abstractions">Define boundaries (and abstractions) +</h4><p>Once again, the goal is generally to achieve some sort of parallelism between your team members. +In order to do that, they need to know how they will interact with each other.</p> +<p>On a more general level, this means knowing the responsibilities and scope of your work.</p> +<p>On a more specific level, it means knowing their dependency graph. +In other words, whether the progress of one team member will depend on the results of another one. +Whenever there is a dependency, the interface should be made very clear. +This often takes the form of an API, a file with a given format, or a section of a document.</p> +<p>Take some time to define the boundary as precisely as needed at that point in the project. +I would suggest having specific examples that you can discuss and modify. +It is hard to discuss in the abstract, especially for inexperienced contributors. +When in doubt, default to the simplest option (e.g., a common file vs using a database). +Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there. +Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.</p> +<p>One type of failure I&rsquo;ve seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples. +That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.</p> +<h4 id="be-approachable">Be approachable +</h4><p>Did I wrote a whole section about autonomy? Yes. +Is the end goal to do more and talk less? Also yes. +Thing is, no process is perfect, and misunderstandings are bound to happen at some point. +If your only response to questions is a grumpy face or a &ldquo;read the freaking docs&rdquo;, your students will not alert you when something really needs your attention, and you will find out too late. +For instance, the documentation may be unclear, or your processes may be inadvertedly alienating new members and making new hires harder.</p> +<p>Another way to be approachable is to be clear about your shortcomings, and whether something you are saying is negotiable and/or debatable. +My rule of thumb is to err on the side of negotiable, and only be strict when it is really necessary (e.g., time constraints or an unproductive student). +We are all more likely to finish our tasks if we feel them ours, if we a say in how and when to perform them.</p> +<p>Just to be clear, approachable does not mean you have to be their confident or their best friend. +It also does not mean that it is okay to challenge or question you continuously. +Some times it is okay to simply say &ldquo;just do as I say&rdquo;.</p> +<h4 id="review-frequently">Review frequently +</h4><p>One type of review is individual. +It involves reviewing code on github, or reading deliverables and papers on overleaf. +It can help catch misunderstandings, and measure the true rate of progress in the individual tasks. +The other type of review is done as a group, by going through the key progress and action points. +This type of review helps everyone stay on the same page, and catch any general drifts in the project, such as misaligned priorities.</p> +<p>The frequency of each type of review depends on the specific nature of the project, the types of tasks being performed by the student, and your confidence on the student&rsquo;s abilities.</p> +<h3 id="optimizing-your-contribution">Optimizing your contribution +</h3><p>Tips on optimizing your contribution to the team.</p> +<h4 id="prioritize-prioritize-prioritize">Prioritize, prioritize, prioritize +</h4><p>Part of your job as a project lead is to identify the main goals in a project and to prioritize the tasks that will lead you there. +On the other hand, you are part of a research group, and you should be actively involved in its health and future. +Lastly, you are also in charge of the life-long project that is your research career.</p> +<p>In all these cases, your goal should be to identify the long term goals, come up with a sound strategy, and prioritize the tasks that will lead you and your group there. +Keeping your priorities straight will help you make steady progress, and avoid bikeshedding and changing goalposts. +It will also help you steer your progress in the right direction, since we all have limited time and effort and can&rsquo;t do everything at once.</p> +<p>The fact that your time is limited also means that you will need to decide how to prioritize these three roles. +I&rsquo;ve listed them in increasing level of importance for me. +It means that it is okay to focus on a specific project for a while, but if progress in your career is stalled - usually through publication - you need to reevaluate and concentrate your efforts on that.</p> +<h4 id="be-okay-with-short-term-inefficiencies">Be okay with (short-term) inefficiencies +</h4><p>I&rsquo;ve personally struggled with delegating tasks that will take me orders of magnitude less work than they will a student. +Thing is, most tasks will fall under this category, and your time is limited, so you have to delegate if you want to have time for more important matters. +If you never delegate any tasks, you are not allowing your team to learn and catch up on whatever technical skills are required. +Besides, you are not improving yourself on the managerial side of things. +It turns out delegating is hard, it requires a whole set of non-technical skills. +I suspect this is oftentimes the reason we don&rsquo;t do delegate in the first place: delegating is hard, and technical tasks are usually more straightforward, so we just don&rsquo;t want to do the work.</p> +<h4 id="dont-neglect-training">Don&rsquo;t neglect training +</h4><p>You are a senior researcher. +You probably know how to solve problems in your domain quite efficiently. +In my case, that means processing data and developing code.</p> +<p>That means I could dedicate my days to processing data and developing new code for my group. +That group would likely be used in multiple projects. +However, there is a hard limit to how much code I can push out in a day, especially if you take into account other obligations such as teaching.</p> +<p>A wiser strategy would be to set aside some of that coding time to instead help students become better programmers. +Firstly, because those students will be thankful and more motivated to work than when they are left to learn on their own without much guidance. +Secondly, because those students will then be more prepared to help me out if I delegate a task to them. +And, lastly, because these students have a whole life in fron of them. +A life full of big projects of their own, and contributions to society. +That little training time can have a compounding effect in the future.</p> +<h4 id="set-a-time-limit-for-your-interactions-in-advance">Set a time limit for your interactions in advance +</h4><p>Really long slots can easily lead to bikeshedding and going unnecessarily deep into implementation details, which is clearly an inefficient use of your time. +Even worse, our attention span and memory are finite, so longer and dense meetings can lead to fatigue and to missing or dilluting important points in the conversation.</p> +<p>For these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary. +You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.</p> +<h2 id="beyond-your-team">Beyond your team +</h2><p>The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control. +But teams rarely work in isolation, you will most likely +In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization&rsquo;s culture and sense of belonging.</p> +<p>Many of the aspects I talked about in the team section apply here. +For instance, the obsession with documentation can - and should - be applied organization-wise. +The same goes for defining boundaries and using concrete examples when collaborating with other teams. +For most intents and purposes, you can treat other teams as another contributor to your team. +Just one that will be more costly and slow to interact with.</p> +<p>If possible, I&rsquo;d try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved. +Avoid involving whole teams in discussions when the broad strokes have not been defined yet. +The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.</p> +<p>On the organization&rsquo;s side, I would suggest having an honest conversation about your core principles. +I really liked <a class="link" href="https://www.youtube.com/watch?v=9QMGAtxUlAchttps://www.youtube.com/watch?v=9QMGAtxUlAc" target="_blank" rel="noopener" +>Bryan Cantrill&rsquo;s talk about principles of technology leadership</a>. +He goes deep into the effects that principles have had on well known companies, and how to go about defining your company&rsquo;s principles. +I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.</p> +<p>More generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive. +And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier. +Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.</p> \ No newline at end of file diff --git a/tags/management/page/1/index.html b/tags/management/page/1/index.html new file mode 100644 index 0000000..ce886b8 --- /dev/null +++ b/tags/management/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/management/ + \ No newline at end of file diff --git a/tags/mouse/index.html b/tags/mouse/index.html new file mode 100644 index 0000000..b52d00b --- /dev/null +++ b/tags/mouse/index.html @@ -0,0 +1,33 @@ +Tag: Mouse - J. Fernando Sánchez +

Tags

1 page

Mouse

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/mouse/index.xml b/tags/mouse/index.xml new file mode 100644 index 0000000..1781f5d --- /dev/null +++ b/tags/mouse/index.xml @@ -0,0 +1,45 @@ +Mouse on J. Fernando Sánchezhttps://balkian.com/tags/mouse/Recent content in Mouse on J. Fernando SánchezHugo -- gohugo.ioen-usSat, 30 Oct 2021 00:00:01 +0000Logitech MB850 combi in linuxhttps://balkian.com/p/logitech-mb850-combi-in-linux/Sat, 30 Oct 2021 00:00:01 +0000https://balkian.com/p/logitech-mb850-combi-in-linux/<p>As a follow-up to my last post, I&rsquo;ve decided to also configure my mk850 combo (k850 + m720 triathlon).</p> +<p>Some notes:</p> +<ul> +<li>The keyboard is usually connected to this PC through bluetooth. Since this is a change I usually do in the system for every keyboard, I added a rule for any bus (usb, bluetooth, etc)</li> +<li>The mouse has an additional button that registers as a keyboard. Every press maps to three key events. I&rsquo;ve disabled two of them and mapped the action to F19, in case I want to use it in my DE/WM.</li> +</ul> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">evdev:input:* </span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_70039</span><span class="o">=</span><span class="s">leftctrl # bind capslock to w </span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">evdev:input:b0005v046DpB015*</span> +</span></span><span class="line"><span class="cl"> <span class="na">KEYBOARD_KEY_700e0</span><span class="o">=</span><span class="s">f19 +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_700e2=unknown +</span></span></span><span class="line"><span class="cl"><span class="s"> KEYBOARD_KEY_7002b=unknown</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>After that, simply run:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"> sudo udevadm hwdb --update &amp;&amp; sudo udevadm trigger +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the settings have been applied by running <code>evemu-describe</code>:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> sudo /sbin/evemu-describe /dev/input/event&lt;id of your device&gt; <span class="p">|</span> grep KEY_ +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/mouse/page/1/index.html b/tags/mouse/page/1/index.html new file mode 100644 index 0000000..5256810 --- /dev/null +++ b/tags/mouse/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/mouse/ + \ No newline at end of file diff --git a/tags/mqtt/index.html b/tags/mqtt/index.html new file mode 100644 index 0000000..1f6aa88 --- /dev/null +++ b/tags/mqtt/index.html @@ -0,0 +1,33 @@ +Tag: Mqtt - J. Fernando Sánchez +

Tags

1 page

Mqtt

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/mqtt/index.xml b/tags/mqtt/index.xml new file mode 100644 index 0000000..76f6364 --- /dev/null +++ b/tags/mqtt/index.xml @@ -0,0 +1,132 @@ +Mqtt on J. Fernando Sánchezhttps://balkian.com/tags/mqtt/Recent content in Mqtt on J. Fernando SánchezHugo -- gohugo.ioen-usSun, 06 Jan 2019 10:00:00 +0000Controlling Zigbee devices with MQTThttps://balkian.com/p/controlling-zigbee-devices-with-mqtt/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/p/controlling-zigbee-devices-with-mqtt/<p>This is a short tutorial on connecting a zigbee device (an Aqara cube) +to an MQTT server, so you can control your zigbee devices from the +network.</p> +<p>If you&rsquo;re anything like me, you&rsquo;re probably a sucker for IoT devices. +For a long time, I&rsquo;ve been using WiFi-enabled lights, and Amazon dash +buttons to control them. To keep these (cheap Chinese) internet enabled +devices away from your network and their respective cloud services, +you&rsquo;ll probably want to set up a dedicated network in your router (more +on this on a future post, maybe). Another disadvantage of WiFi devices +is that they&rsquo;re relatively power hungry.</p> +<p>A popular alternative is using ZigBee for communication. It is a +dedicated protocol similar to bluetooth (BLE), with lower power +requirements and bitrate.</p> +<p>Take the (super cute) aqara cube as an example. It is a small cube that +detects rotation on all of its axes, and tapping events. Here&rsquo;s a +video:</p> +<div class="video-wrapper"> +<iframe loading="lazy" +src="https://www.youtube.com/embed/5YtqG1wEnng" +allowfullscreen +title="YouTube Video" +> +</iframe> +</div> +<p>To connect to zigbee devices you will need a zigbee enabled gateway +(a.k.a. hub), which connects to your WiFi network and your zigbee +devices. Once again, this means adding an internet-enabled device to +your home, and probably a couple of cloud services.</p> +<p>As an alternative, you can set up your own zigbee gateway, and control +it to your home automation platform of choice (e.g. home assistant). We +will cover how to set up a zigbee2mqtt gateway that is also connected to +an MQTT server, so you can use MQTT to control your devices and get +notifications.</p> +<p>What you need:</p> +<ul> +<li><a class="link" href="https://www.aliexpress.com/item/Original-Xiaomi-Mi-Aqara-Cube-Smart-Home-Controller-6-Action-Operation-Fr-Home-Device-Zigbee-Version/32892947622.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>Aqara +cube</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/Wireless-Zigbee-CC2531-CC2540-Zigbee-Sniffer-Bluetooth-BLE-4-0-Dongle-Capture-Module-USB-Programmer-Downloader/32907587711.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC2531 zigbee +sniffer</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/CFSUNBIRD-CC-DEBUGGER-Debugger-and-Programmer-for-RF-System-on-Chips-TI-ORIGINAL-Fast-hipping/32813122315.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC-debugger</a>.</li> +</ul> +<p>You will need to flash your sniffer. For that, you only need to follow +the instructions from the <a class="link" href="https://koenkk.github.io/zigbee2mqtt/" target="_blank" rel="noopener" +>zigbee2mqtt +documentation</a>.</p> +<p>Once you&rsquo;re done flashing, you&rsquo;re ready to set up the zigbee2mqtt +server. For convenience, I wrote a simple docker-compose to deploy a +zigbee2mqtt server and a test mosquitto server:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;2.1&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">zigbee2mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">koenkk/zigbee2mqtt</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">zigbee2mqtt </span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./z2m-data/:/app/data/</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">devices</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="s2">&#34;/dev/ttyACM0&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">eclipse-mosquitto</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">1883</span><span class="p">:</span><span class="m">1883</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">9001</span><span class="p">:</span><span class="m">9001</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./mosquitto.conf:/mosquitto/config/mosquitto.conf</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hass</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">overlay</span><span class="w"> +</span></span></span></code></pre></td></tr></table> +</div> +</div><p>You can test your installation with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">❯ mosquitto_sub -h localhost -p <span class="m">1883</span> -t <span class="s1">&#39;zigbee2mqtt/#&#39;</span> +</span></span><span class="line"><span class="cl">online +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:149,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;rotate_right&#34;</span>,<span class="s2">&#34;angle&#34;</span>:12.8<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;slide&#34;</span>,<span class="s2">&#34;side&#34;</span>:2<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:120<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;wakeup&#34;</span><span class="o">}</span></span></span></code></pre></td></tr></table> +</div> +</div> +<p>zigbee2mqtt supports the following events for the aqara cube: shake, +wakeup, fall, tap, slide, flip180, flip90, rotate_left and +rotate_right. Every event has additional information, such as the sides +involved, or the degrees turned.</p> +<p>Now you are ready to set up home assistant support in zigbee2mqtt +following <a class="link" href="https://koenkk.github.io/zigbee2mqtt/integration/home_assistant.html" target="_blank" rel="noopener" +>this +guide</a>.</p> \ No newline at end of file diff --git a/tags/mqtt/page/1/index.html b/tags/mqtt/page/1/index.html new file mode 100644 index 0000000..890dfcf --- /dev/null +++ b/tags/mqtt/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/mqtt/ + \ No newline at end of file diff --git a/tags/nginx/index.html b/tags/nginx/index.html new file mode 100644 index 0000000..c8b5551 --- /dev/null +++ b/tags/nginx/index.html @@ -0,0 +1,33 @@ +Tag: Nginx - J. Fernando Sánchez +

Tags

1 page

Nginx

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/nginx/index.xml b/tags/nginx/index.xml new file mode 100644 index 0000000..0383521 --- /dev/null +++ b/tags/nginx/index.xml @@ -0,0 +1,142 @@ +Nginx on J. Fernando Sánchezhttps://balkian.com/tags/nginx/Recent content in Nginx on J. Fernando SánchezHugo -- gohugo.ioen-usTue, 09 Dec 2014 12:12:12 +0000Zoterohttps://balkian.com/p/zotero/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/p/zotero/<p><a class="link" href="https://www.zotero.org/" target="_blank" rel="noopener" +>Zotero</a> is an Open Source tool that lets you +organise your bibliography, syncing it with the cloud. Unlike other +alternatives such as <a class="link" href="http://www.mendeley.com" target="_blank" rel="noopener" +>Mendeley</a>, Zotero can +upload the attachments and data to a private cloud via WebDav.</p> +<p>If you use nginx as your web server, know that even though it provides +partial support for webdav, Zotero needs more than that. Hence, you will +need another webdav server, and optionally let nginx proxy to it. This +short post provides the basics to get that set-up working under +Debian/Ubuntu.</p> +<h2 id="setting-up-apache">Setting up Apache +</h2><p>First we need to install Apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install apache2 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Change the head of &ldquo;/etc/apache2/sites-enabled/000-default&rdquo; to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;VirtualHost</span> <span class="s">*:880</span><span class="nt">&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Then, create a file /etc/apache2/sites-available/webdav:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nb">Alias</span> <span class="sx">/dav</span> <span class="sx">/home/webdav/dav</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/dav</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">on</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Order</span> Allow,Deny +</span></span><span class="line"><span class="cl"> <span class="nb">Allow</span> from <span class="k">all</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">On</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Options</span> +Indexes +</span></span><span class="line"><span class="cl"> <span class="nb">AuthType</span> Basic +</span></span><span class="line"><span class="cl"> <span class="nb">AuthName</span> DAV +</span></span><span class="line"><span class="cl"> <span class="nb">AuthBasicProvider</span> file +</span></span><span class="line"><span class="cl"> <span class="nb">AuthUserFile</span> <span class="sx">/home/webdav/.htpasswd</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Require</span> valid-user +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Ideally, you want your webdav folders to be private, adding +authentication to them. So you need to create the webdav and zotero +users and add the passwords to an htpasswd file. Even though you could +use a single user, since you will be configuring several clients with +your credentials I encourage you to create the zotero user as well. This +way you can always change the password for zotero without affecting any +other application using webdav.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo adduser webdav +</span></span><span class="line"><span class="cl">sudo htpasswd -c /home/webdav/.htpasswd webdav +</span></span><span class="line"><span class="cl">sudo htpasswd /home/webdav/.htpasswd zotero +</span></span><span class="line"><span class="cl">sudo mkdir -p /home/webdav/dav/zotero +</span></span></code></pre></td></tr></table> +</div> +</div><p>Enable the site and restart apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo a2enmod webdav +</span></span><span class="line"><span class="cl">sudo a2enmod dav_fs +</span></span><span class="line"><span class="cl">sudo a2ensite webdav +</span></span><span class="line"><span class="cl">sudo service apache2 restart +</span></span></code></pre></td></tr></table> +</div> +</div><p>At this point everything should be working at +<a class="link" href="http://" target="_blank" rel="noopener" +>http://</a>&lt;your_host&gt;:880/dav/zotero</p> +<h2 id="setting-up-nginx">Setting up NGINX +</h2><p>After the Apache side is working, we can use nginx as a proxy to get +cleaner URIs. In your desired site/location, add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/dav</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="kn">client_max_body_size</span> <span class="s">20M</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:880</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now just reload nginx:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo service nginx force-reload +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extras">Extras +</h2><ul> +<li><a class="link" href="http://zoteroreader.com/" target="_blank" rel="noopener" +>Zotero Reader</a> - HTML5 client</li> +<li><a class="link" href="https://github.com/ajlyon/zandy" target="_blank" rel="noopener" +>Zandy</a> - Android Open Source +client</li> +</ul> \ No newline at end of file diff --git a/tags/nginx/page/1/index.html b/tags/nginx/page/1/index.html new file mode 100644 index 0000000..bdbc632 --- /dev/null +++ b/tags/nginx/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/nginx/ + \ No newline at end of file diff --git a/tags/nix/index.html b/tags/nix/index.html new file mode 100644 index 0000000..0b22750 --- /dev/null +++ b/tags/nix/index.html @@ -0,0 +1,33 @@ +Tag: Nix - J. Fernando Sánchez +

Tags

1 page

Nix

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/nix/index.xml b/tags/nix/index.xml new file mode 100644 index 0000000..4ad16af --- /dev/null +++ b/tags/nix/index.xml @@ -0,0 +1,97 @@ +Nix on J. Fernando Sánchezhttps://balkian.com/tags/nix/Recent content in Nix on J. Fernando SánchezHugo -- gohugo.ioen-usMon, 13 Nov 2023 18:21:46 +0100Nix Recipe for Python Projectshttps://balkian.com/p/nix-recipe-for-python-projects/Mon, 13 Nov 2023 18:21:46 +0100https://balkian.com/p/nix-recipe-for-python-projects/<p>This is a quick and easy recipe to add a <code>default.nix</code> to any Python project with a <code>requirements.txt</code> file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="k">with</span> <span class="kn">import</span> <span class="sr">&lt;nixpkgs&gt;</span> <span class="p">{</span> <span class="p">};</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">let</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span> <span class="o">=</span> <span class="n">python311Packages</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="k">rec</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;impurePythonEnv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">venvDir</span> <span class="o">=</span> <span class="s2">&#34;./.venv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">buildInputs</span> <span class="o">=</span> <span class="p">[</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># A python interpreter including the &#39;venv&#39; module is required to bootstrap</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the environment.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">python</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># This execute some shell code to initialize a venv in $venvDir before</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># dropping into the shell</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">venvShellHook</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Those are dependencies that we would like to use from nixpkgs, which will</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># add them to PYTHONPATH and thus make them accessible from within the venv.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">numpy</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">requests</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># In this particular example, in order to compile any binary extensions they may</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># require, the python modules listed in the hypothetical requirements.txt need</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the following packages to be installed locally:</span> +</span></span><span class="line"><span class="cl"> <span class="n">taglib</span> +</span></span><span class="line"><span class="cl"> <span class="n">openssl</span> +</span></span><span class="line"><span class="cl"> <span class="n">git</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxml2</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxslt</span> +</span></span><span class="line"><span class="cl"> <span class="n">libzip</span> +</span></span><span class="line"><span class="cl"> <span class="n">zlib</span> +</span></span><span class="line"><span class="cl"> <span class="p">];</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Now we can execute any commands within the virtual environment.</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># This is optional and can be left out to run pip manually.</span> +</span></span><span class="line"><span class="cl"> <span class="n">postShellHook</span> <span class="o">=</span> <span class="s1">&#39;&#39; +</span></span></span><span class="line"><span class="cl"><span class="s1"> pip install -r requirements.txt +</span></span></span><span class="line"><span class="cl"><span class="s1"> &#39;&#39;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now, you will get a clean environment by running:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">nix-shell +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/nix/page/1/index.html b/tags/nix/page/1/index.html new file mode 100644 index 0000000..f26c1b7 --- /dev/null +++ b/tags/nix/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/nix/ + \ No newline at end of file diff --git a/tags/org/index.html b/tags/org/index.html new file mode 100644 index 0000000..ac584be --- /dev/null +++ b/tags/org/index.html @@ -0,0 +1,33 @@ +Tag: Org - J. Fernando Sánchez +

Tags

1 page

Org

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/org/index.xml b/tags/org/index.xml new file mode 100644 index 0000000..a11fc80 --- /dev/null +++ b/tags/org/index.xml @@ -0,0 +1,9 @@ +Org on J. Fernando Sánchezhttps://balkian.com/tags/org/Recent content in Org on J. Fernando SánchezHugo -- gohugo.ioen-usEmacs: show plain text versionhttps://balkian.com/p/emacs-show-plain-text-version/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/emacs-show-plain-text-version/<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">font-lock-mode</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/org/page/1/index.html b/tags/org/page/1/index.html new file mode 100644 index 0000000..fa7f605 --- /dev/null +++ b/tags/org/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/org/ + \ No newline at end of file diff --git a/tags/page/1/index.html b/tags/page/1/index.html new file mode 100644 index 0000000..da632d9 --- /dev/null +++ b/tags/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/ + \ No newline at end of file diff --git a/tags/page/2/index.html b/tags/page/2/index.html new file mode 100644 index 0000000..81027bd --- /dev/null +++ b/tags/page/2/index.html @@ -0,0 +1,37 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/page/3/index.html b/tags/page/3/index.html new file mode 100644 index 0000000..6cd8873 --- /dev/null +++ b/tags/page/3/index.html @@ -0,0 +1,38 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/page/4/index.html b/tags/page/4/index.html new file mode 100644 index 0000000..e828803 --- /dev/null +++ b/tags/page/4/index.html @@ -0,0 +1,39 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/page/5/index.html b/tags/page/5/index.html new file mode 100644 index 0000000..6c9fc60 --- /dev/null +++ b/tags/page/5/index.html @@ -0,0 +1,39 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/page/6/index.html b/tags/page/6/index.html new file mode 100644 index 0000000..5d3b393 --- /dev/null +++ b/tags/page/6/index.html @@ -0,0 +1,39 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/page/7/index.html b/tags/page/7/index.html new file mode 100644 index 0000000..369f600 --- /dev/null +++ b/tags/page/7/index.html @@ -0,0 +1,38 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/page/8/index.html b/tags/page/8/index.html new file mode 100644 index 0000000..a0a90d0 --- /dev/null +++ b/tags/page/8/index.html @@ -0,0 +1,37 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/page/9/index.html b/tags/page/9/index.html new file mode 100644 index 0000000..aeb64c4 --- /dev/null +++ b/tags/page/9/index.html @@ -0,0 +1,36 @@ +Tags +

Section

42 pages

Tags

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/productivity/index.html b/tags/productivity/index.html new file mode 100644 index 0000000..3b92c7a --- /dev/null +++ b/tags/productivity/index.html @@ -0,0 +1,33 @@ +Tag: Productivity - J. Fernando Sánchez +

Tags

1 page

Productivity

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/productivity/index.xml b/tags/productivity/index.xml new file mode 100644 index 0000000..957551f --- /dev/null +++ b/tags/productivity/index.xml @@ -0,0 +1,9 @@ +Productivity on J. Fernando Sánchezhttps://balkian.com/tags/productivity/Recent content in Productivity on J. Fernando SánchezHugo -- gohugo.ioen-usEmacs: show plain text versionhttps://balkian.com/p/emacs-show-plain-text-version/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/emacs-show-plain-text-version/<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">font-lock-mode</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/productivity/page/1/index.html b/tags/productivity/page/1/index.html new file mode 100644 index 0000000..4921f2a --- /dev/null +++ b/tags/productivity/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/productivity/ + \ No newline at end of file diff --git a/tags/programming/index.html b/tags/programming/index.html new file mode 100644 index 0000000..bc3132a --- /dev/null +++ b/tags/programming/index.html @@ -0,0 +1,33 @@ +Tag: Programming - J. Fernando Sánchez +

Tags

1 page

Programming

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/programming/index.xml b/tags/programming/index.xml new file mode 100644 index 0000000..9060feb --- /dev/null +++ b/tags/programming/index.xml @@ -0,0 +1,26 @@ +Programming on J. Fernando Sánchezhttps://balkian.com/tags/programming/Recent content in Programming on J. Fernando SánchezHugo -- gohugo.ioen-usPythonhttps://balkian.com/cheatsheet/python/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/python/<img src="https://balkian.com/img/python.png" alt="Featured image of post Python" /><h2 id="interesting-libraries">Interesting libraries +</h2><h3 id="tqdm"><a class="link" href="https://github.com/tqdm/tqdm" target="_blank" rel="noopener" +>TQDM</a> +</h3><p>From tqdm&rsquo;s github repository:</p> +<blockquote> +<p>tqdm means &ldquo;progress&rdquo; in Arabic (taqadum, تقدّم) and an abbreviation for &ldquo;I love you so much&rdquo; in Spanish (te quiero demasiado).</p></blockquote> +<p><img src="https://raw.githubusercontent.com/tqdm/tqdm/master/images/tqdm.gif" +loading="lazy" +alt="TQDM in action" +></p> +<h2 id="tools">Tools +</h2><h3 id="uv"><a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a> +</h3><p>🚀 A single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +⚡️ 10-100x faster than pip.</p> +<ul> +<li>Provides comprehensive project management, with a universal lockfile.</li> +<li>Runs scripts, with support for inline dependency metadata.</li> +<li>Installs and manages Python versions.</li> +<li>Runs and installs tools published as Python packages.</li> +<li>Includes a pip-compatible interface for a performance boost with a familiar CLI.</li> +<li>Supports Cargo-style workspaces for scalable projects.</li> +<li>Disk-space efficient, with a global cache for dependency deduplication.</li> +<li>Installable without Rust or Python via curl or pip.</li> +<li>Supports macOS, Linux, and Windows.</li> +</ul> \ No newline at end of file diff --git a/tags/programming/page/1/index.html b/tags/programming/page/1/index.html new file mode 100644 index 0000000..c949be7 --- /dev/null +++ b/tags/programming/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/programming/ + \ No newline at end of file diff --git a/tags/proxy/index.html b/tags/proxy/index.html new file mode 100644 index 0000000..e7585d7 --- /dev/null +++ b/tags/proxy/index.html @@ -0,0 +1,33 @@ +Tag: Proxy - J. Fernando Sánchez +

Tags

1 page

Proxy

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/proxy/index.xml b/tags/proxy/index.xml new file mode 100644 index 0000000..b5cd5c0 --- /dev/null +++ b/tags/proxy/index.xml @@ -0,0 +1,105 @@ +Proxy on J. Fernando Sánchezhttps://balkian.com/tags/proxy/Recent content in Proxy on J. Fernando SánchezHugo -- gohugo.ioen-usThu, 09 Oct 2014 10:00:00 +0000Proxies with Apache and pythonhttps://balkian.com/p/proxies-with-apache-and-python/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/p/proxies-with-apache-and-python/<p>This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name <em>application</em>. Hence, wsgi:application.</p> +<h2 id="gunicorn">Gunicorn +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPass</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPassReverse</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">RequestHeader</span> set SCRIPT_NAME <span class="s2">&#34;/myapp/&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p><strong>Important</strong>: <em>SCRIPT_NAME</em> and the end of <em>ProxyPass</em> URL <strong>MUST BE +THE SAME</strong>. Otherwise, Gunicorn will fail miserably.</p> +<p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">venv/bin/gunicorn -w <span class="m">4</span> -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="uwsgi">UWSGI +</h2><p>This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">SetHandler</span> uwsgi_handler +</span></span><span class="line"><span class="cl"> <span class="nb">uWSGISocker</span> <span class="m">127.0.0.1</span>:8888 +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uwsgi --socket 127.0.0.1:8888 -w wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extra-supervisor">Extra: Supervisor +</h2><p>If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[unix_http_server]</span> +</span></span><span class="line"><span class="cl"><span class="na">file</span><span class="o">=</span><span class="s">/tmp/myapp.sock ; path to your socket file</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisord]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisor.log</span> +</span></span><span class="line"><span class="cl"><span class="na">childlogdir</span> <span class="o">=</span> <span class="s">%(here)s/logs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[rpcinterface:supervisor]</span> +</span></span><span class="line"><span class="cl"><span class="na">supervisor.rpcinterface_factory</span> <span class="o">=</span> <span class="s">supervisor.rpcinterface:make_main_rpcinterface</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisorctl]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisorctl.log</span> +</span></span><span class="line"><span class="cl"><span class="na">serverurl</span><span class="o">=</span><span class="s">unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[program:myapp]</span> +</span></span><span class="line"><span class="cl"><span class="na">command</span> <span class="o">=</span> <span class="s">venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application</span> +</span></span><span class="line"><span class="cl"><span class="na">directory</span> <span class="o">=</span> <span class="s">%(here)s</span> +</span></span><span class="line"><span class="cl"><span class="na">environment</span> <span class="o">=</span> <span class="s">PATH=%(here)s/venv/bin/</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/myapp.log</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/proxy/page/1/index.html b/tags/proxy/page/1/index.html new file mode 100644 index 0000000..03d18a5 --- /dev/null +++ b/tags/proxy/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/proxy/ + \ No newline at end of file diff --git a/tags/pydantic/index.html b/tags/pydantic/index.html new file mode 100644 index 0000000..528ce28 --- /dev/null +++ b/tags/pydantic/index.html @@ -0,0 +1,33 @@ +Tag: Pydantic - J. Fernando Sánchez +

Tags

1 page

Pydantic

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/pydantic/index.xml b/tags/pydantic/index.xml new file mode 100644 index 0000000..33692c6 --- /dev/null +++ b/tags/pydantic/index.xml @@ -0,0 +1,96 @@ +Pydantic on J. Fernando Sánchezhttps://balkian.com/tags/pydantic/Recent content in Pydantic on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 26 Feb 2025 23:22:59 +0100Bridging RDF, JSON-LD and Dataclasseshttps://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/<p>In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.</p> +<h2 id="the-rdflib-way">The <code>rdflib</code> way +</h2><p><code>rdflib</code> only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, <code>Entity</code>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">entry</span> <span class="o">=</span> <span class="n">Entry</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">entry</span><span class="p">[</span><span class="s1">&#39;vocab:property&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">jsonld</span><span class="p">())</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Would print something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@id&#34;</span><span class="p">:</span> <span class="s2">&#34;:Entry_202505....&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;prefix:Entity&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;vocab:property&#34;</span><span class="p">:</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the</p> +<p>Pros:</p> +<ul> +<li>Flexible/extensible</li> +<li>Lightweight. This is mostly JSON-LD in Python&rsquo;s clothing.</li> +<li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li> +</ul> +<p>Cons:</p> +<ul> +<li>Discoverability. Documentation and examples are needed to know which attributes to use</li> +<li>Error-prone. It is easy to misuse a property, or introduce typos</li> +<li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li> +</ul> +<h2 id="the-object-oriented-way">The object-oriented way +</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like <a class="link" href="https://pydantic.dev/" target="_blank" rel="noopener" +>pydantic</a> that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p> +<p>This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p> +<p>Pros:</p> +<ul> +<li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li> +<li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li> +</ul> +<p>Cons:</p> +<ul> +<li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li> +<li>Polymorphism.</li> +</ul> +<h2 id="a-hybrid-approach">A hybrid approach +</h2><p>Whichever solution is chosen in the end, it needs to:</p> +<ul> +<li>Make it easy and error-proof to add the most common types of annotations</li> +<li>Allow for additional annotations/attributes to be added</li> +<li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li> +<li>Allow for deserialization of custom types</li> +<li>Allow multiple consumers to add their own annotations</li> +</ul> \ No newline at end of file diff --git a/tags/pydantic/page/1/index.html b/tags/pydantic/page/1/index.html new file mode 100644 index 0000000..4e51cb6 --- /dev/null +++ b/tags/pydantic/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/pydantic/ + \ No newline at end of file diff --git a/tags/pypi/index.html b/tags/pypi/index.html new file mode 100644 index 0000000..97a3204 --- /dev/null +++ b/tags/pypi/index.html @@ -0,0 +1,33 @@ +Tag: Pypi - J. Fernando Sánchez +

Tags

1 page

Pypi

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/pypi/index.xml b/tags/pypi/index.xml new file mode 100644 index 0000000..21e99a7 --- /dev/null +++ b/tags/pypi/index.xml @@ -0,0 +1,162 @@ +Pypi on J. Fernando Sánchezhttps://balkian.com/tags/pypi/Recent content in Pypi on J. Fernando SánchezHugo -- gohugo.ioen-usSat, 27 Sep 2014 10:00:00 +0000Publishing on PyPihttps://balkian.com/p/publishing-on-pypi/Sat, 27 Sep 2014 10:00:00 +0000https://balkian.com/p/publishing-on-pypi/<p>Developing a python module and publishing it on Github is cool, but most +of the times you want others to download and use it easily. That is the +role of PyPi, the python package repository. In this post I show you how +to publish your package in less than 10 minutes.</p> +<h2 id="choose-a-fancy-name">Choose a fancy name +</h2><p>If you haven&rsquo;t done so yet, take a minute or two to think about this. +To publish on PyPi you need a name for your package that isn&rsquo;t taken. +What&rsquo;s more, a catchy and unique name will help people remember your +module and feel more inclined to at least try it.</p> +<p>The package name should hint what your module does, but that&rsquo;s not +always the case. That&rsquo;s your call. I personally put uniqueness and +memorability over describing the functionality.</p> +<h2 id="create-a-pypirc-configuration-file">Create a .pypirc configuration file +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">[distutils] # this tells distutils what package indexes you can push to</span> +</span></span><span class="line"><span class="cl"><span class="na">index-servers</span> <span class="o">=</span><span class="s"> +</span></span></span><span class="line"><span class="cl"><span class="s"> pypi # the live PyPI +</span></span></span><span class="line"><span class="cl"><span class="s"> pypitest # test PyPI</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypi] # authentication details for live PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://pypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span><span class="line"><span class="cl"><span class="na">password</span> <span class="o">=</span> <span class="s">{ your_password } # not necessary</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypitest] # authentication details for test PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://testpypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>As you can see, you need to register both in the <a class="link" href="https://pypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>main pypi +repository</a> and +the <a class="link" href="https://testpypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>testing +server</a>. The +usernames and passwords might be different, that is up to you!</p> +<h2 id="prepare-your-package">Prepare your package +</h2><p>This should be the structure:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">root-dir/ # Any name you want +</span></span><span class="line"><span class="cl"> setup.py +</span></span><span class="line"><span class="cl"> setup.cfg +</span></span><span class="line"><span class="cl"> LICENSE.txt +</span></span><span class="line"><span class="cl"> README.md +</span></span><span class="line"><span class="cl"> mypackage/ +</span></span><span class="line"><span class="cl"> __init__.py +</span></span><span class="line"><span class="cl"> foo.py +</span></span><span class="line"><span class="cl"> bar.py +</span></span><span class="line"><span class="cl"> baz.py +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="setupcfg">setup.cfg +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[metadata]</span> +</span></span><span class="line"><span class="cl"><span class="na">description-file</span> <span class="o">=</span> <span class="s">README.md</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The markdown README is the <em>de facto</em> standard in Github, but you can +also use rST (reStructuredText), the standard in the python community.</p> +<h3 id="setuppy">setup.py +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">distutils.core</span> <span class="kn">import</span> <span class="n">setup</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">setup</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;mypackage&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;mypackage&#39;</span><span class="p">],</span> <span class="c1"># this must be the same as the name above</span> +</span></span><span class="line"><span class="cl"> <span class="n">version</span> <span class="o">=</span> <span class="s1">&#39;{ version }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">description</span> <span class="o">=</span> <span class="s1">&#39;{ description }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">author</span> <span class="o">=</span> <span class="s1">&#39;{ name }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">email</span> <span class="o">=</span> <span class="s1">&#39;{ email }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{package}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="c1"># URL to the github repo</span> +</span></span><span class="line"><span class="cl"> <span class="n">download_url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{repo}</span><span class="s1">/tarball/</span><span class="si">{version}</span><span class="s1">&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">keywords</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;websockets&#39;</span><span class="p">,</span> <span class="s1">&#39;display&#39;</span><span class="p">,</span> <span class="s1">&#39;d3&#39;</span><span class="p">],</span> <span class="c1"># list of keywords that represent your package</span> +</span></span><span class="line"><span class="cl"> <span class="n">classifiers</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>You might notice that the download_url points to a Github URL. We could +host our package anywhere, but Github is a convenient option. To create +the tarball and the zip packages, you only need to tag a tag in your +repository and push it to github:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git tag <span class="o">{</span>version<span class="o">}</span> -m <span class="s2">&#34;{ Description of this tag/version}&#34;</span> +</span></span><span class="line"><span class="cl">git push --tags origin master +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="push-to-the-testingmain-pypi-server">Push to the testing/main pypi server +</h2><p>It is advisable that you try your package on the test repository and fix +any problems first. The process is simple:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">python setup.py register -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> python setup.py sdist upload -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>If everything went as expected, you can now install your package through +pip and browse your package&rsquo;s page. For instance, check my senpy +package: <a class="link" href="https://pypi.python.org/pypi/senpy" target="_blank" rel="noopener" +>https://pypi.python.org/pypi/senpy</a></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install senpy +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/pypi/page/1/index.html b/tags/pypi/page/1/index.html new file mode 100644 index 0000000..7cd8109 --- /dev/null +++ b/tags/pypi/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/pypi/ + \ No newline at end of file diff --git a/tags/python/index.html b/tags/python/index.html new file mode 100644 index 0000000..a6816bd --- /dev/null +++ b/tags/python/index.html @@ -0,0 +1,34 @@ +Tag: Python - J. Fernando Sánchez +

Tags

8 pages

Python

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/python/index.xml b/tags/python/index.xml new file mode 100644 index 0000000..28b03dc --- /dev/null +++ b/tags/python/index.xml @@ -0,0 +1,760 @@ +Python on J. Fernando Sánchezhttps://balkian.com/tags/python/Recent content in Python on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 26 Feb 2025 23:22:59 +0100Bridging RDF, JSON-LD and Dataclasseshttps://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/<p>In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.</p> +<h2 id="the-rdflib-way">The <code>rdflib</code> way +</h2><p><code>rdflib</code> only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, <code>Entity</code>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">entry</span> <span class="o">=</span> <span class="n">Entry</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">entry</span><span class="p">[</span><span class="s1">&#39;vocab:property&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">jsonld</span><span class="p">())</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Would print something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@id&#34;</span><span class="p">:</span> <span class="s2">&#34;:Entry_202505....&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;prefix:Entity&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;vocab:property&#34;</span><span class="p">:</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the</p> +<p>Pros:</p> +<ul> +<li>Flexible/extensible</li> +<li>Lightweight. This is mostly JSON-LD in Python&rsquo;s clothing.</li> +<li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li> +</ul> +<p>Cons:</p> +<ul> +<li>Discoverability. Documentation and examples are needed to know which attributes to use</li> +<li>Error-prone. It is easy to misuse a property, or introduce typos</li> +<li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li> +</ul> +<h2 id="the-object-oriented-way">The object-oriented way +</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like <a class="link" href="https://pydantic.dev/" target="_blank" rel="noopener" +>pydantic</a> that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p> +<p>This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p> +<p>Pros:</p> +<ul> +<li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li> +<li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li> +</ul> +<p>Cons:</p> +<ul> +<li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li> +<li>Polymorphism.</li> +</ul> +<h2 id="a-hybrid-approach">A hybrid approach +</h2><p>Whichever solution is chosen in the end, it needs to:</p> +<ul> +<li>Make it easy and error-proof to add the most common types of annotations</li> +<li>Allow for additional annotations/attributes to be added</li> +<li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li> +<li>Allow for deserialization of custom types</li> +<li>Allow multiple consumers to add their own annotations</li> +</ul>uv - One rust tool to rule all pythonshttps://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/Mon, 17 Feb 2025 23:02:47 +0100https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/<img src="https://balkian.com/img/uv.png" alt="Featured image of post uv - One rust tool to rule all pythons" /><p>Long story short: I&rsquo;m now using <a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a>, and so should you. +It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.</p> +<h2 id="context">Context +</h2><p>For years, my strategy to manage python projects has been a mix of a custom <code>setup.py</code>, several hand-crafted <code>requirements.txt</code> files (through <code>pip freeze</code>), a custom virtualenv per project, and multiple tools to upload to PyPI. +Although this works, this setup has many drawbacks:</p> +<ul> +<li>It requires user intervention (creating a venv, sourcing it, handling new deps). This isn&rsquo;t ideal if you want new (probably inexperienced) users to use your projects.</li> +<li>On a similar note, the whole process needs to be well documented if you want other users to contribute or maintain the code.</li> +<li>Pinning dependency versions is finicky, and I&rsquo;ve run into problems beause of that.</li> +<li>Creating a new project involves a template, or copying files from an older project.</li> +</ul> +<p>Of course, this is nothing new. +There is a whole site dedicated to <a class="link" href="https://packaging.python.org/en/latest/" target="_blank" rel="noopener" +>packaging your Python project</a>. +A plethora of different projects have come and go, with varying degrees of success.</p> +<h2 id="alternatives-poetry">Alternatives (poetry) +</h2><p>About a year before trying <code>uv</code>, I tried to catch up with the ecosystem and get to know the <code>blessed new way</code>. +However, the task proved to be a little more difficult, as the landscape is filled with a myriad of alternatives, each with their own set of drawbacks and detractors. +Packaging has historically been a weak spot, in ironical contradiction to the Zen of Python&rsquo;s &ldquo;There should be one&ndash; and preferably only one &ndash;obvious way to do it&rdquo;,</p> +<p>I eventually settled on <a class="link" href="https://python-poetry.org/" target="_blank" rel="noopener" +>poetry</a>. +Mostly because it seemed like the most popular alternative.</p> +<p>There are many things I liked about it. +First of all, having a convention for dependencies (<code>pyproject.toml</code>) and a tool that properly handles them was nice. +It also removed the need to remember specific incantations to build and publish my Python projects. +Lastly, I mixed it <code>poetry2nix</code> to create reproducible python environments using nix. +This makes for a very powerful experience.</p> +<p>However, there were multiple hiccups. +First of all, it took me some time to figure out which specific fields to use (each tool can define ad-hoc properties in a the <code>pyproject.toml</code> file), and some of them seemed redundant with the more generic ones. +Full disclosure, this specific point might be a mistake on my side, and I do not remember the details. +The second one is speed. +(Re-)creating an environment took a non-negligible amount of time.</p> +<h2 id="enter-light-uv">Enter <del>light</del> <code>uv</code> +</h2><p>According to its repository, <code>uv </code>can replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +Not only that, but it also claims to do that 10-100 times faster than pip. +I must admit that it being written in rust was a another selling point for me, as I&rsquo;m looking for excuses to collaborate in a decently-sized rust projejct.</p> +<p>Installing it is dead simple: simply download the binary (e.g., with curl) or run <code>pip install uv</code>. +You won&rsquo;t need much more: <code>uv</code> seems to just do the right thing out of the box. +And it does it really, really fast. +The rest of the time it gets out of the way.</p> +<p>My only gripe so far is that I don&rsquo;t seem to find a built-in command to drop into a shell, but that is nothing that <code>uv run $SHELL</code> cannot fix.</p> +<h2 id="common-operations">Common operations +</h2><h3 id="initialize-a-repository">Initialize a repository +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv init +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="adding-dependencies">Adding dependencies +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv add senpy +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="running-commands-inside-the-environment">Running commands inside the environment +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv run &lt;COMMAND&gt; +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"># e.g., run a shell using your python version and dependencies +</span></span><span class="line"><span class="cl">uv run $SHELL +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="dependency-tree">Dependency tree +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">uv shell +</span></span><span class="line"><span class="cl">Resolved 44 packages in 1ms +</span></span><span class="line"><span class="cl">my-project v0.1.0 +</span></span><span class="line"><span class="cl">├── fastapi[standard] v0.115.8 +</span></span><span class="line"><span class="cl">│ ├── pydantic v2.10.6 +</span></span><span class="line"><span class="cl">│ │ ├── annotated-types v0.7.0 +</span></span><span class="line"><span class="cl">│ │ ├── pydantic-core v2.27.2 +</span></span><span class="line"><span class="cl">│ │ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── starlette v0.45.3 +</span></span><span class="line"><span class="cl">│ │ └── anyio v4.8.0 +</span></span><span class="line"><span class="cl">│ │ ├── exceptiongroup v1.2.2 +</span></span><span class="line"><span class="cl">│ │ ├── idna v3.10 +</span></span><span class="line"><span class="cl">│ │ ├── sniffio v1.3.1 +</span></span><span class="line"><span class="cl">│ │ └── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── typing-extensions v4.12.2 +</span></span><span class="line"><span class="cl">│ ├── email-validator v2.2.0 (extra: standard) +</span></span><span class="line"><span class="cl">│ │ ├── dnspython v2.7.0 +</span></span><span class="line"><span class="cl">... +</span></span></code></pre></td></tr></table> +</div> +</div>Nix Recipe for Python Projectshttps://balkian.com/p/nix-recipe-for-python-projects/Mon, 13 Nov 2023 18:21:46 +0100https://balkian.com/p/nix-recipe-for-python-projects/<p>This is a quick and easy recipe to add a <code>default.nix</code> to any Python project with a <code>requirements.txt</code> file:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="k">with</span> <span class="kn">import</span> <span class="sr">&lt;nixpkgs&gt;</span> <span class="p">{</span> <span class="p">};</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">let</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span> <span class="o">=</span> <span class="n">python311Packages</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="k">rec</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;impurePythonEnv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">venvDir</span> <span class="o">=</span> <span class="s2">&#34;./.venv&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="n">buildInputs</span> <span class="o">=</span> <span class="p">[</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># A python interpreter including the &#39;venv&#39; module is required to bootstrap</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the environment.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">python</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># This execute some shell code to initialize a venv in $venvDir before</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># dropping into the shell</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">venvShellHook</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Those are dependencies that we would like to use from nixpkgs, which will</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># add them to PYTHONPATH and thus make them accessible from within the venv.</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">numpy</span> +</span></span><span class="line"><span class="cl"> <span class="n">pythonPackages</span><span class="o">.</span><span class="n">requests</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># In this particular example, in order to compile any binary extensions they may</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># require, the python modules listed in the hypothetical requirements.txt need</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># the following packages to be installed locally:</span> +</span></span><span class="line"><span class="cl"> <span class="n">taglib</span> +</span></span><span class="line"><span class="cl"> <span class="n">openssl</span> +</span></span><span class="line"><span class="cl"> <span class="n">git</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxml2</span> +</span></span><span class="line"><span class="cl"> <span class="n">libxslt</span> +</span></span><span class="line"><span class="cl"> <span class="n">libzip</span> +</span></span><span class="line"><span class="cl"> <span class="n">zlib</span> +</span></span><span class="line"><span class="cl"> <span class="p">];</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># Now we can execute any commands within the virtual environment.</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># This is optional and can be left out to run pip manually.</span> +</span></span><span class="line"><span class="cl"> <span class="n">postShellHook</span> <span class="o">=</span> <span class="s1">&#39;&#39; +</span></span></span><span class="line"><span class="cl"><span class="s1"> pip install -r requirements.txt +</span></span></span><span class="line"><span class="cl"><span class="s1"> &#39;&#39;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now, you will get a clean environment by running:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">nix-shell +</span></span></code></pre></td></tr></table> +</div> +</div>Progress bars in pythonhttps://balkian.com/p/progress-bars-in-python/Wed, 28 Sep 2016 18:47:00 +0000https://balkian.com/p/progress-bars-in-python/<p><a class="link" href="https://github.com/noamraph/tqdm" target="_blank" rel="noopener" +>tqdm</a> is a nice way to add progress +bars in the command line or in a jupyter notebook.</p> +<p><img src="https://camo.githubusercontent.com/48838faaa8d00ea297f18e5bf55d3c6bb4e0ba6b/68747470733a2f2f692e696d6775722e636f6d2f686539417735432e676966" +loading="lazy" +alt="image" +></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">tqdm</span> <span class="kn">import</span> <span class="n">tqdm</span> +</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)):</span> +</span></span><span class="line"><span class="cl"> <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Proxies with Apache and pythonhttps://balkian.com/p/proxies-with-apache-and-python/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/p/proxies-with-apache-and-python/<p>This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name <em>application</em>. Hence, wsgi:application.</p> +<h2 id="gunicorn">Gunicorn +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPass</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPassReverse</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">RequestHeader</span> set SCRIPT_NAME <span class="s2">&#34;/myapp/&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p><strong>Important</strong>: <em>SCRIPT_NAME</em> and the end of <em>ProxyPass</em> URL <strong>MUST BE +THE SAME</strong>. Otherwise, Gunicorn will fail miserably.</p> +<p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">venv/bin/gunicorn -w <span class="m">4</span> -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="uwsgi">UWSGI +</h2><p>This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">SetHandler</span> uwsgi_handler +</span></span><span class="line"><span class="cl"> <span class="nb">uWSGISocker</span> <span class="m">127.0.0.1</span>:8888 +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uwsgi --socket 127.0.0.1:8888 -w wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extra-supervisor">Extra: Supervisor +</h2><p>If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[unix_http_server]</span> +</span></span><span class="line"><span class="cl"><span class="na">file</span><span class="o">=</span><span class="s">/tmp/myapp.sock ; path to your socket file</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisord]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisor.log</span> +</span></span><span class="line"><span class="cl"><span class="na">childlogdir</span> <span class="o">=</span> <span class="s">%(here)s/logs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[rpcinterface:supervisor]</span> +</span></span><span class="line"><span class="cl"><span class="na">supervisor.rpcinterface_factory</span> <span class="o">=</span> <span class="s">supervisor.rpcinterface:make_main_rpcinterface</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisorctl]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisorctl.log</span> +</span></span><span class="line"><span class="cl"><span class="na">serverurl</span><span class="o">=</span><span class="s">unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[program:myapp]</span> +</span></span><span class="line"><span class="cl"><span class="na">command</span> <span class="o">=</span> <span class="s">venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application</span> +</span></span><span class="line"><span class="cl"><span class="na">directory</span> <span class="o">=</span> <span class="s">%(here)s</span> +</span></span><span class="line"><span class="cl"><span class="na">environment</span> <span class="o">=</span> <span class="s">PATH=%(here)s/venv/bin/</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/myapp.log</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Publishing on PyPihttps://balkian.com/p/publishing-on-pypi/Sat, 27 Sep 2014 10:00:00 +0000https://balkian.com/p/publishing-on-pypi/<p>Developing a python module and publishing it on Github is cool, but most +of the times you want others to download and use it easily. That is the +role of PyPi, the python package repository. In this post I show you how +to publish your package in less than 10 minutes.</p> +<h2 id="choose-a-fancy-name">Choose a fancy name +</h2><p>If you haven&rsquo;t done so yet, take a minute or two to think about this. +To publish on PyPi you need a name for your package that isn&rsquo;t taken. +What&rsquo;s more, a catchy and unique name will help people remember your +module and feel more inclined to at least try it.</p> +<p>The package name should hint what your module does, but that&rsquo;s not +always the case. That&rsquo;s your call. I personally put uniqueness and +memorability over describing the functionality.</p> +<h2 id="create-a-pypirc-configuration-file">Create a .pypirc configuration file +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="na">[distutils] # this tells distutils what package indexes you can push to</span> +</span></span><span class="line"><span class="cl"><span class="na">index-servers</span> <span class="o">=</span><span class="s"> +</span></span></span><span class="line"><span class="cl"><span class="s"> pypi # the live PyPI +</span></span></span><span class="line"><span class="cl"><span class="s"> pypitest # test PyPI</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypi] # authentication details for live PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://pypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span><span class="line"><span class="cl"><span class="na">password</span> <span class="o">=</span> <span class="s">{ your_password } # not necessary</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="na">[pypitest] # authentication details for test PyPI</span> +</span></span><span class="line"><span class="cl"><span class="na">repository</span> <span class="o">=</span> <span class="s">https://testpypi.python.org/pypi</span> +</span></span><span class="line"><span class="cl"><span class="na">username</span> <span class="o">=</span> <span class="s">{ your_username }</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>As you can see, you need to register both in the <a class="link" href="https://pypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>main pypi +repository</a> and +the <a class="link" href="https://testpypi.python.org/pypi?%3Aaction=register_form" target="_blank" rel="noopener" +>testing +server</a>. The +usernames and passwords might be different, that is up to you!</p> +<h2 id="prepare-your-package">Prepare your package +</h2><p>This should be the structure:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">root-dir/ # Any name you want +</span></span><span class="line"><span class="cl"> setup.py +</span></span><span class="line"><span class="cl"> setup.cfg +</span></span><span class="line"><span class="cl"> LICENSE.txt +</span></span><span class="line"><span class="cl"> README.md +</span></span><span class="line"><span class="cl"> mypackage/ +</span></span><span class="line"><span class="cl"> __init__.py +</span></span><span class="line"><span class="cl"> foo.py +</span></span><span class="line"><span class="cl"> bar.py +</span></span><span class="line"><span class="cl"> baz.py +</span></span></code></pre></td></tr></table> +</div> +</div><h3 id="setupcfg">setup.cfg +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[metadata]</span> +</span></span><span class="line"><span class="cl"><span class="na">description-file</span> <span class="o">=</span> <span class="s">README.md</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>The markdown README is the <em>de facto</em> standard in Github, but you can +also use rST (reStructuredText), the standard in the python community.</p> +<h3 id="setuppy">setup.py +</h3><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">distutils.core</span> <span class="kn">import</span> <span class="n">setup</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">setup</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;mypackage&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;mypackage&#39;</span><span class="p">],</span> <span class="c1"># this must be the same as the name above</span> +</span></span><span class="line"><span class="cl"> <span class="n">version</span> <span class="o">=</span> <span class="s1">&#39;{ version }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">description</span> <span class="o">=</span> <span class="s1">&#39;{ description }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">author</span> <span class="o">=</span> <span class="s1">&#39;{ name }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">email</span> <span class="o">=</span> <span class="s1">&#39;{ email }&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{package}</span><span class="s1">&#39;</span><span class="p">,</span> <span class="c1"># URL to the github repo</span> +</span></span><span class="line"><span class="cl"> <span class="n">download_url</span> <span class="o">=</span> <span class="s1">&#39;https://github.com/</span><span class="si">{user}</span><span class="s1">/</span><span class="si">{repo}</span><span class="s1">/tarball/</span><span class="si">{version}</span><span class="s1">&#39;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="n">keywords</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;websockets&#39;</span><span class="p">,</span> <span class="s1">&#39;display&#39;</span><span class="p">,</span> <span class="s1">&#39;d3&#39;</span><span class="p">],</span> <span class="c1"># list of keywords that represent your package</span> +</span></span><span class="line"><span class="cl"> <span class="n">classifiers</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>You might notice that the download_url points to a Github URL. We could +host our package anywhere, but Github is a convenient option. To create +the tarball and the zip packages, you only need to tag a tag in your +repository and push it to github:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git tag <span class="o">{</span>version<span class="o">}</span> -m <span class="s2">&#34;{ Description of this tag/version}&#34;</span> +</span></span><span class="line"><span class="cl">git push --tags origin master +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="push-to-the-testingmain-pypi-server">Push to the testing/main pypi server +</h2><p>It is advisable that you try your package on the test repository and fix +any problems first. The process is simple:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">python setup.py register -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> python setup.py sdist upload -r <span class="o">{</span>pypitest/pypi<span class="o">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>If everything went as expected, you can now install your package through +pip and browse your package&rsquo;s page. For instance, check my senpy +package: <a class="link" href="https://pypi.python.org/pypi/senpy" target="_blank" rel="noopener" +>https://pypi.python.org/pypi/senpy</a></p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install senpy +</span></span></code></pre></td></tr></table> +</div> +</div>Updating EuroLoveMaphttps://balkian.com/p/updating-eurolovemap/Thu, 27 Mar 2014 14:00:00 +0000https://balkian.com/p/updating-eurolovemap/<p>As part of the <a class="link" href="http://www.opener-project.org/2013/07/18/opener-hackathon-in-amsterdam/" target="_blank" rel="noopener" +>OpeNER +hackathon</a> +we decided to build a prototype that would allow us to compare how +different countries feel about several topics. We used the OpeNER +pipeline to get the sentiment from a set of newspaper articles we +gathered from media in several languages. Then we aggregated those +articles by category and country (using the source of the article or the +language it was written in), obtaining the &ldquo;overall feeling&rdquo; of each +country about each topic. Then, we used some fancy JavaScript to make +sense out of the raw information.</p> +<p>It didn&rsquo;t go too bad, it turns out <a class="link" href="http://eurosentiment.eu/wp-content/uploads/2013/07/BOLv9qnCIAAJEek.jpg" target="_blank" rel="noopener" +>we +won</a>.</p> +<p>Now, it was time for a face-lift. I used this opportunity to play with +new technologies and improve it:</p> +<ul> +<li>Using Flask, this time using python 3.3 and Bootstrap 3.0</li> +<li>Cool HTML5+JS cards (thanks to +<a class="link" href="http://pastetophone.com" target="_blank" rel="noopener" +>pastetophone</a>)</li> +<li>Automatic generation of fake personal data to test the interface</li> +<li>Obfuscation of personal emails</li> +</ul> +<p>The result can be <a class="link" href="http://eurolovemap.herokuapp.com/" target="_blank" rel="noopener" +>seen here</a>.</p> +<h2 id="publishing-a-python-3-app-on-heroku">Publishing a Python 3 app on Heroku +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkvirtualenv -p /usr/bin/python3.3 eurolovemap +</span></span></code></pre></td></tr></table> +</div> +</div><p>Since Heroku uses python 2.7 by default, we have to tell it which +version we want, although it supports python 3.4 as well. I couldn&rsquo;t +get python 3.4 working using the +<a class="link" href="https://launchpad.net/~fkrull/&#43;archive/deadsnakes" target="_blank" rel="noopener" +>deadsnakes</a> ppa, so +I used python 3.3 instead, which works fine but is not officially +supported. Just create a file named <em>runtime.txt</em> in your project root, +with the python version you want to use:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python-3.3.1 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Don&rsquo;t forget to freeze your dependencies so Heroku can install them: +<code>bash pip freze &gt; requirements.txt</code></p> +<h2 id="publishing-personal-emails">Publishing personal emails +</h2><p>There are really sophisticated and effective ways to obfuscate personal +emails so that spammers cannot easily grab yours. However, this time I +needed something really simple to hide our emails from the simplest form +of crawlers. Most of the team are in academia somehow, so in the end all +our emails are available in sites like Google Scholar. Anyway, nobody +likes getting spammed so I settled for a custom <a class="link" href="http://en.wikipedia.org/wiki/Caesar_cipher" target="_blank" rel="noopener" +>Caesar +cipher</a>. Please, don&rsquo;t use +it for any serious application if you are concerned about being spammed.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">blur_email</span><span class="p">(</span><span class="n">email</span><span class="p">):</span> +</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">email</span><span class="p">])</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>And this is the client side:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByClassName</span><span class="p">(</span><span class="s1">&#39;profile-email&#39;</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">e</span> <span class="k">in</span> <span class="nx">elems</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">blur</span> <span class="o">=</span> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">email</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">s</span> <span class="k">in</span> <span class="nx">blur</span><span class="p">){</span> +</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">blur</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="o">+</span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="nx">a</span><span class="o">-</span><span class="mi">5</span><span class="p">);</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"> <span class="nx">elems</span><span class="p">[</span><span class="nx">e</span><span class="p">].</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="p">}</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Unfortunately, this approach does not hide your email from anyone using +<a class="link" href="http://phantomjs.org/" target="_blank" rel="noopener" +>PhantomJS</a>, +<a class="link" href="http://zombie.labnotes.org/" target="_blank" rel="noopener" +>ZombieJS</a> or similar. For that, other +approaches like generating a picture with the address would be +necessary. Nevertheless, it is overkill for a really simple ad-hoc +application with custom formatting and just a bunch of emails that would +easily be grabbed manually.</p> +<h2 id="generation-of-fake-data">Generation of fake data +</h2><p>To test the contact section of the site, I wanted to populate it with +fake data. <a class="link" href="https://github.com/joke2k/faker" target="_blank" rel="noopener" +>Fake-Factory</a> is an amazing +library that can generate fake data of almost any kind: emails, +association names, acronyms&hellip; It even lets you localise the results +(get Spanish names, for instance) and generate factories for certain +classes (à la Django).</p> +<p>But I also wanted pictures, enter <a class="link" href="http://lorempixel.com/" target="_blank" rel="noopener" +>Lorem Pixel</a>. +With its API you can generate pictures of almost any size, for different +topics (e.g. nightlife, people) and with a custom text. You can even use +an index, so it will always show the same picture.</p> +<p>For instance, the picture below is served through Lorem Pixel.</p> +<p><img src="http://lorempixel.com/400/200/nightlife/" +loading="lazy" +></p> +<p>By the way, if you only want cat pictures, take a look at +<a class="link" href="http://placekitten.com/" target="_blank" rel="noopener" +>Placekitten</a>. And for NSFW text, there&rsquo;s the +<a class="link" href="http://slipsum.com/" target="_blank" rel="noopener" +>Samuel L. Jackson Ipsum</a></p>Pythonhttps://balkian.com/cheatsheet/python/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/cheatsheet/python/<img src="https://balkian.com/img/python.png" alt="Featured image of post Python" /><h2 id="interesting-libraries">Interesting libraries +</h2><h3 id="tqdm"><a class="link" href="https://github.com/tqdm/tqdm" target="_blank" rel="noopener" +>TQDM</a> +</h3><p>From tqdm&rsquo;s github repository:</p> +<blockquote> +<p>tqdm means &ldquo;progress&rdquo; in Arabic (taqadum, تقدّم) and an abbreviation for &ldquo;I love you so much&rdquo; in Spanish (te quiero demasiado).</p></blockquote> +<p><img src="https://raw.githubusercontent.com/tqdm/tqdm/master/images/tqdm.gif" +loading="lazy" +alt="TQDM in action" +></p> +<h2 id="tools">Tools +</h2><h3 id="uv"><a class="link" href="https://github.com/astral-sh/uv" target="_blank" rel="noopener" +>uv</a> +</h3><p>🚀 A single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more. +⚡️ 10-100x faster than pip.</p> +<ul> +<li>Provides comprehensive project management, with a universal lockfile.</li> +<li>Runs scripts, with support for inline dependency metadata.</li> +<li>Installs and manages Python versions.</li> +<li>Runs and installs tools published as Python packages.</li> +<li>Includes a pip-compatible interface for a performance boost with a familiar CLI.</li> +<li>Supports Cargo-style workspaces for scalable projects.</li> +<li>Disk-space efficient, with a global cache for dependency deduplication.</li> +<li>Installable without Rust or Python via curl or pip.</li> +<li>Supports macOS, Linux, and Windows.</li> +</ul> \ No newline at end of file diff --git a/tags/python/page/1/index.html b/tags/python/page/1/index.html new file mode 100644 index 0000000..2d8dee1 --- /dev/null +++ b/tags/python/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/python/ + \ No newline at end of file diff --git a/tags/python/page/2/index.html b/tags/python/page/2/index.html new file mode 100644 index 0000000..f54b932 --- /dev/null +++ b/tags/python/page/2/index.html @@ -0,0 +1,34 @@ +Tag: Python - Pager 2 - J. Fernando Sánchez +

Tags

8 pages

Python

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/rdf/index.html b/tags/rdf/index.html new file mode 100644 index 0000000..0be57be --- /dev/null +++ b/tags/rdf/index.html @@ -0,0 +1,33 @@ +Tag: Rdf - J. Fernando Sánchez +

Tags

1 page

Rdf

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/rdf/index.xml b/tags/rdf/index.xml new file mode 100644 index 0000000..4b104fe --- /dev/null +++ b/tags/rdf/index.xml @@ -0,0 +1,96 @@ +Rdf on J. Fernando Sánchezhttps://balkian.com/tags/rdf/Recent content in Rdf on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 26 Feb 2025 23:22:59 +0100Bridging RDF, JSON-LD and Dataclasseshttps://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/Wed, 26 Feb 2025 23:22:59 +0100https://balkian.com/p/bridging-rdf-json-ld-and-dataclasses/<p>In the RDF world, data is expressed as a collection of triples. +These triples can contain IRIs that may or may not be accessible or valid. +And the use of these IRIs may or may not adhere to a vocabulary. +Checking the validity of the IRIs and the semantics of the triples is an additional step.</p> +<h2 id="the-rdflib-way">The <code>rdflib</code> way +</h2><p><code>rdflib</code> only models IRIs, values and namespaces. +Developers need to be cognisant of the URIs they are using, and the vocabularies being used. +Prior to version 2.0, senpy followed a very similar model. +It had a base class to represent a generic node. +Each instance then gets its own automatically generated id, and will act like a normal dictionary, whose keys and values will be serialized as a JSON-LD dictionary. +Multiple subclasses were also included to model specific types of node, mostly to provide convenience methods for the given subtype. +Here is an example of a subclass, <code>Entity</code>.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">entry</span> <span class="o">=</span> <span class="n">Entry</span><span class="p">()</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="n">entry</span><span class="p">[</span><span class="s1">&#39;vocab:property&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">jsonld</span><span class="p">())</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Would print something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@id&#34;</span><span class="p">:</span> <span class="s2">&#34;:Entry_202505....&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;prefix:Entity&#34;</span><span class="p">,</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&#34;vocab:property&#34;</span><span class="p">:</span> <span class="mi">25</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Producing correct triples using this model requires using the vocabularies and URIs properly, with little to no tooling to enforce it. +This poses a big problem for a tool like Senpy, which aims to make it easier for professionals without a background in RDF to build and consume semantic NLP ser +If an attribute is not a URI and is not included in the global JSON-LD context, it will not generate a triple in the final graph. +Moreover, there is way to enforce that the vocabularies and the</p> +<p>Pros:</p> +<ul> +<li>Flexible/extensible</li> +<li>Lightweight. This is mostly JSON-LD in Python&rsquo;s clothing.</li> +<li>Naturally maps to both <code>rdflib</code> and writing <code>json-ld</code></li> +</ul> +<p>Cons:</p> +<ul> +<li>Discoverability. Documentation and examples are needed to know which attributes to use</li> +<li>Error-prone. It is easy to misuse a property, or introduce typos</li> +<li>Tight coupling with semantics/RDF. One needs to know a thing or two about RDF, especially if new vocabularies or annotations need to be used.</li> +</ul> +<h2 id="the-object-oriented-way">The object-oriented way +</h2><p>An obvious alternative to this problem in an object-oriented language like python is to use classes to represent our data model. +These classes can define the specific attributes available, and typing annotations can serve both as a guide for the developer, and as a means to automatically +validate objects at runtime. +There are tools like <a class="link" href="https://pydantic.dev/" target="_blank" rel="noopener" +>pydantic</a> that make this process very simple. +Then, we only need to define how your models should be serialized into JSON-LD. +We can thoroughly test this serialization to ensure that the resulting object is correct and produces the right RDF graph. +Going back to our previous example, we could define an Entry class as a dataclass, and define all the possible types of annotations as attributes.</p> +<p>This model works great when all the possible attributes are known ahead of time. +But it starts to break when the model provided is not comprehensive enough, or customers of your library need to provide their own ad-hoc annotations / attribut +es. +This could be solved by encouring consumers of our library to define their own subclasses whenever they need to add new attributes. +This works perfectly fine for serialization, but it breaks if your library needs to automatically deserialize these subclasses. +It also breaks if different parts of the code need to add their own attributes on the same data at the same time. +This was precisely the case of <code>senpy</code>, where entities are annotated by different plugins, each providing a different set of annotations.</p> +<p>Pros:</p> +<ul> +<li>Discoverability. All possible attributes are known ahead of time, including their possible types.</li> +<li>Decoupling from RDF. Developers only need to know about the dataclasses provided. The mapping to the RDF world is already encoded in the dataclass.</li> +</ul> +<p>Cons:</p> +<ul> +<li>Rigidity. Adding new types of annotations requires modifying the models, in the main module.</li> +<li>Polymorphism.</li> +</ul> +<h2 id="a-hybrid-approach">A hybrid approach +</h2><p>Whichever solution is chosen in the end, it needs to:</p> +<ul> +<li>Make it easy and error-proof to add the most common types of annotations</li> +<li>Allow for additional annotations/attributes to be added</li> +<li>Allow for upgrades in the future. i.e., converting the most common custom annotations into built-in ones</li> +<li>Allow for deserialization of custom types</li> +<li>Allow multiple consumers to add their own annotations</li> +</ul> \ No newline at end of file diff --git a/tags/rdf/page/1/index.html b/tags/rdf/page/1/index.html new file mode 100644 index 0000000..2191b09 --- /dev/null +++ b/tags/rdf/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/rdf/ + \ No newline at end of file diff --git a/tags/rpi/index.html b/tags/rpi/index.html new file mode 100644 index 0000000..f7b29a0 --- /dev/null +++ b/tags/rpi/index.html @@ -0,0 +1,33 @@ +Tag: Rpi - J. Fernando Sánchez +

Tags

1 page

Rpi

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/rpi/index.xml b/tags/rpi/index.xml new file mode 100644 index 0000000..c4448a3 --- /dev/null +++ b/tags/rpi/index.xml @@ -0,0 +1,20 @@ +Rpi on J. Fernando Sánchezhttps://balkian.com/tags/rpi/Recent content in Rpi on J. Fernando SánchezHugo -- gohugo.ioen-usFixing HDMI flickeringhttps://balkian.com/p/fixing-hdmi-flickering/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/fixing-hdmi-flickering/<img src="https://balkian.com/img/rpi.png" alt="Featured image of post Fixing HDMI flickering" /><p>Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_drive</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_group</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_mode</span><span class="o">=</span><span class="mi">42</span> +</span></span><span class="line"><span class="cl"> <span class="n">disable_overscan</span><span class="o">=</span><span class="mi">1</span> +</span></span><span class="line"><span class="cl"> <span class="n">config_hdmi_boost</span><span class="o">=</span><span class="mi">7</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/rpi/page/1/index.html b/tags/rpi/page/1/index.html new file mode 100644 index 0000000..d3ebfc6 --- /dev/null +++ b/tags/rpi/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/rpi/ + \ No newline at end of file diff --git a/tags/ruby/index.html b/tags/ruby/index.html new file mode 100644 index 0000000..01f824e --- /dev/null +++ b/tags/ruby/index.html @@ -0,0 +1,33 @@ +Tag: Ruby - J. Fernando Sánchez +

Tags

1 page

Ruby

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/ruby/index.xml b/tags/ruby/index.xml new file mode 100644 index 0000000..4d0726f --- /dev/null +++ b/tags/ruby/index.xml @@ -0,0 +1,48 @@ +Ruby on J. Fernando Sánchezhttps://balkian.com/tags/ruby/Recent content in Ruby on J. Fernando SánchezHugo -- gohugo.ioen-usThu, 22 Aug 2013 14:14:22 +0000Creating my webhttps://balkian.com/p/creating-my-web/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/p/creating-my-web/<p>I&rsquo;ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +<a class="link" href="http://jekyllrb.com" target="_blank" rel="noopener" +>Jekyll</a> will automatically generate static pages +for my posts every time I commit anything new to this repository.</p> +<p>But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.</p> +<p>I hadn&rsquo;t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven&rsquo;t included +any Javascript code in the page. Probably I will use some to add my +<a class="link" href="http://gist.github.com/balkian" target="_blank" rel="noopener" +>gists</a> and +<a class="link" href="http://github.com/balkian" target="_blank" rel="noopener" +>repositories</a>, but we will see about that.</p> +<p>I think the code speaks for itself, so you can check out <a class="link" href="http://github.com/balkian/balkian.github.com" target="_blank" rel="noopener" +>my repository +on Github</a>. You can clone +and deploy it easily like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone +</span></span><span class="line"><span class="cl">https://github.com/balkian/balkian.github.com <span class="nb">cd</span> balkian.github.com +</span></span><span class="line"><span class="cl">jekyll serve -w +</span></span></code></pre></td></tr></table> +</div> +</div><p>I will keep updating this post with information about:</p> +<ul> +<li>Some Jekyll plugins that might be useful</li> +<li>What CSS tricks I learnt</li> +<li>The webfonts I used</li> +<li>The badge on the left side of the page</li> +</ul> \ No newline at end of file diff --git a/tags/ruby/page/1/index.html b/tags/ruby/page/1/index.html new file mode 100644 index 0000000..bc0618d --- /dev/null +++ b/tags/ruby/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/ruby/ + \ No newline at end of file diff --git a/tags/rust/index.html b/tags/rust/index.html new file mode 100644 index 0000000..9c463a9 --- /dev/null +++ b/tags/rust/index.html @@ -0,0 +1,33 @@ +Tag: Rust - J. Fernando Sánchez +

Tags

1 page

Rust

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/rust/index.xml b/tags/rust/index.xml new file mode 100644 index 0000000..4eccebc --- /dev/null +++ b/tags/rust/index.xml @@ -0,0 +1,189 @@ +Rust on J. Fernando Sánchezhttps://balkian.com/tags/rust/Recent content in Rust on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 20 Jan 2023 18:11:00 +0000Kanata: advanced keyboard configurationhttps://balkian.com/p/kanata-advanced-keyboard-configuration/Fri, 20 Jan 2023 18:11:00 +0000https://balkian.com/p/kanata-advanced-keyboard-configuration/<p><a class="link" href="https://github.com/jtroo/kanata/" target="_blank" rel="noopener" +>Kanata</a> is a software keyboard remapper that aims to improve keyboard comfort and usability with advanced customization. +Keyboard remappers are a good alternative to running a custom keyboard with QMK/ZMK, and have two main advantages: they work on any keyboard, and you can configure them to launch any command or program you want, not just key presses. +On the other hand, you need to configure them on every PC/OS you&rsquo;re using your keyboard with, and all the processing is done on software on top of the OS, so there may be glitches and performance issues.</p> +<p>The project was inspired by the more popular <a class="link" href="https://github.com/kmonad/kmonad" target="_blank" rel="noopener" +>KMonad</a>, and the author cites some of the <a class="link" href="https://github.com/jtroo/kanata/blob/main/docs/kmonad_comparison.md" target="_blank" rel="noopener" +>differences</a>. +Both projects use a very similar configuration format based on lisp. +The configuration consists of a set of general options, a base key configuration, a series of layers, and macros that can be used within those layers. +<a class="link" href="https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd" target="_blank" rel="noopener" +>Here&rsquo;s a very complete config that serves as documentation</a>.</p> +<p>One big disadvantage of the lispy configuration is that you need to specify your hardware layout/all your keys, and repeat that every time you define a new layer. +The result visually maps to your keyboard, but can be very verbose/big if you need really few changes.</p> +<p><a class="link" href="https://github.com/rvaiya/keyd/" target="_blank" rel="noopener" +>Keyd</a> is another alternative with a more declarative configuration format, which might lend itself to smaller.</p> +<p>For now I&rsquo;m just trying it out, and getting a feel for using fewer keys before I build my own ZMK keyboard. +I particularly like the option of using mod-keys on the home row (e.g., having A work as a CTRL when held). +Mod-tap, tap-dancing and the like are very common techniques in sub-40% layouts, where there simply aren&rsquo;t enough keys for all the letters and symbols. +In a regular-sized keyboard, these techniques can also help you stay on the home row and type more comfortably. +At least, that&rsquo;s the idea. +We&rsquo;ll see if I like it enough to stick with it.</p> +<p>For now, here&rsquo;s my very simple config:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span><span class="lnt">25 +</span><span class="lnt">26 +</span><span class="lnt">27 +</span><span class="lnt">28 +</span><span class="lnt">29 +</span><span class="lnt">30 +</span><span class="lnt">31 +</span><span class="lnt">32 +</span><span class="lnt">33 +</span><span class="lnt">34 +</span><span class="lnt">35 +</span><span class="lnt">36 +</span><span class="lnt">37 +</span><span class="lnt">38 +</span><span class="lnt">39 +</span><span class="lnt">40 +</span><span class="lnt">41 +</span><span class="lnt">42 +</span><span class="lnt">43 +</span><span class="lnt">44 +</span><span class="lnt">45 +</span><span class="lnt">46 +</span><span class="lnt">47 +</span><span class="lnt">48 +</span><span class="lnt">49 +</span><span class="lnt">50 +</span><span class="lnt">51 +</span><span class="lnt">52 +</span><span class="lnt">53 +</span><span class="lnt">54 +</span><span class="lnt">55 +</span><span class="lnt">56 +</span><span class="lnt">57 +</span><span class="lnt">58 +</span><span class="lnt">59 +</span><span class="lnt">60 +</span><span class="lnt">61 +</span><span class="lnt">62 +</span><span class="lnt">63 +</span><span class="lnt">64 +</span><span class="lnt">65 +</span><span class="lnt">66 +</span><span class="lnt">67 +</span><span class="lnt">68 +</span><span class="lnt">69 +</span><span class="lnt">70 +</span><span class="lnt">71 +</span><span class="lnt">72 +</span><span class="lnt">73 +</span><span class="lnt">74 +</span><span class="lnt">75 +</span><span class="lnt">76 +</span><span class="lnt">77 +</span><span class="lnt">78 +</span><span class="lnt">79 +</span><span class="lnt">80 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defcfg</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Your keyboard device will likely differ from this.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">linux-dev</span> <span class="nv">/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Windows doesn&#39;t need any input/output configuration entries; however, there</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; must still be a defcfg entry. You can keep the linux-dev entry or delete</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; it and leave it empty.</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defsrc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="nf">-</span> <span class="nf">=</span> <span class="nv">bspc</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">e</span> <span class="nv">r</span> <span class="no">t</span> <span class="nv">y</span> <span class="nv">u</span> <span class="nv">i</span> <span class="nv">o</span> <span class="nv">p</span> <span class="nv">[</span> <span class="nv">]</span> +</span></span><span class="line"><span class="cl"> <span class="nv">caps</span> <span class="nv">a</span> <span class="nv">s</span> <span class="nv">d</span> <span class="nv">f</span> <span class="nv">g</span> <span class="nv">h</span> <span class="nv">j</span> <span class="nv">k</span> <span class="nv">l</span> <span class="c1">; &#39; ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">\ z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">v</span> <span class="nv">b</span> <span class="nv">n</span> <span class="nv">m</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctl</span> <span class="nv">lmet</span> <span class="nv">lalt</span> <span class="nv">spc</span> <span class="nv">ralt</span> <span class="nv">rmet</span> <span class="nv">rctl</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">qwerty</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@warrows</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@slsft</span> <span class="nv">@dlalt</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@jrmet</span> <span class="nv">@kralt</span> <span class="nv">@lrsft</span> <span class="nv">@</span><span class="c1">;rctrl _ _</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">arrows</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@flmet</span> <span class="nv">_</span> <span class="nv">left</span> <span class="nv">down</span> <span class="nv">up</span> <span class="nv">rght</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">@smartspace</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">colemak</span> +</span></span><span class="line"><span class="cl"> <span class="nv">grv</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tab</span> <span class="nv">q</span> <span class="nv">w</span> <span class="nv">f</span> <span class="nv">p</span> <span class="nv">b</span> <span class="nv">j</span> <span class="nv">l</span> <span class="nv">u</span> <span class="nv">y</span> <span class="c1">; [ ] </span> +</span></span><span class="line"><span class="cl"> <span class="nv">lctrl</span> <span class="nv">@alctrl</span> <span class="nv">@rlsft</span> <span class="nv">@slalt</span> <span class="nv">@tlmet</span> <span class="nv">g</span> <span class="nv">m</span> <span class="nv">@nrmet</span> <span class="nv">@eralt</span> <span class="nv">@irsft</span> <span class="nv">@orctrl</span> <span class="o">&#39;</span> <span class="nv">ret</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lsft</span> <span class="nv">XX</span> <span class="nv">z</span> <span class="nv">x</span> <span class="nv">c</span> <span class="nv">d</span> <span class="nv">v</span> <span class="nv">k</span> <span class="nv">h</span> <span class="o">,</span> <span class="o">.</span> <span class="nf">/</span> <span class="nv">rsft</span> +</span></span><span class="line"><span class="cl"> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">@smartspace</span> <span class="nv">XX</span> <span class="nv">XX</span> <span class="nv">XX</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">deflayer</span> <span class="nv">magic</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">@clmk</span> <span class="nv">@qwerty</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">tab</span> <span class="nv">A-tab</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">bspc</span> <span class="nv">esc</span> <span class="nv">_</span> <span class="nv">ret</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> <span class="nv">_</span> +</span></span><span class="line"><span class="cl"><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">defalias</span> +</span></span><span class="line"><span class="cl"> <span class="nv">warrows</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">w</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">arrows</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">alctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">a</span> <span class="nv">lctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">dlalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">d</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">flmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">f</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">jrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">j</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">k</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">lrsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">l</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;rctrl (tap-hold 200 200 ; rctrl)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">rlsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">r</span> <span class="nv">lsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">slalt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">s</span> <span class="nv">lalt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">tlmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="no">t</span> <span class="nv">lmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">nrmet</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">n</span> <span class="nv">rmet</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">eralt</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">e</span> <span class="nv">ralt</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">irsft</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">i</span> <span class="nv">rsft</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">orctrl</span> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">200</span> <span class="mi">200</span> <span class="nv">o</span> <span class="nv">rctrl</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">clmk</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">colemak</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">qwerty</span> <span class="p">(</span><span class="nv">layer-switch</span> <span class="nv">qwerty</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nv">smartspace</span> <span class="p">(</span><span class="nv">tap-dance</span> <span class="mi">200</span> <span class="p">(</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="nv">spc</span> <span class="p">(</span><span class="nv">layer-toggle</span> <span class="nv">magic</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">tap-hold</span> <span class="mi">300</span> <span class="mi">300</span> <span class="p">(</span><span class="nv">one-shot</span> <span class="mi">300</span> <span class="nv">lalt</span><span class="p">)</span> <span class="nv">spc</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="nv">a</span> +</span></span><span class="line"><span class="cl"> <span class="p">))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/rust/page/1/index.html b/tags/rust/page/1/index.html new file mode 100644 index 0000000..ed94d37 --- /dev/null +++ b/tags/rust/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/rust/ + \ No newline at end of file diff --git a/tags/semantic-web/index.html b/tags/semantic-web/index.html new file mode 100644 index 0000000..5d1386b --- /dev/null +++ b/tags/semantic-web/index.html @@ -0,0 +1,33 @@ +Tag: Semantic Web - J. Fernando Sánchez +

Tags

1 page

Semantic Web

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/semantic-web/index.xml b/tags/semantic-web/index.xml new file mode 100644 index 0000000..d86dc25 --- /dev/null +++ b/tags/semantic-web/index.xml @@ -0,0 +1,10 @@ +Semantic Web on J. Fernando Sánchezhttps://balkian.com/tags/semantic-web/Recent content in Semantic Web on J. Fernando SánchezHugo -- gohugo.ioen-usFri, 07 Mar 2025 10:24:52 +0100RDF Is Deadhttps://balkian.com/p/rdf-is-dead/Fri, 07 Mar 2025 10:24:52 +0100https://balkian.com/p/rdf-is-dead/<p>A big part of my research has been around vocabularies and semantic annotation. +And, to be honest, I&rsquo;ve grown increasingly dissatisfied with the field. +To the point where I dread having to work on it. +Some day I will write about it in length, but today I&rsquo;ve stumbled upon a post that covers the topic quite well: <a class="link" href="https://terminusdb.com/blog/the-semantic-web-is-dead/" target="_blank" rel="noopener" +>The Semantic Web is Dead - <del>Long Live the Semantic Web</del></a> (styling mine).</p> +<p>In particular, this section has really resonated with me:</p> +<blockquote class="note"><h1 id="academics-and-industry">Academics and Industry +</h1><p>The political economy of academia and its interaction with industry is the origin of our current lack of a functional Semantic Web.</p> +<p>Academia is structured in a way that there is very little incentive for anyone to build usable software. Instead, you are elevated for rapidly throwing together an idea, a tiny proof of concept, and to iterate on microscopic variations of this thing to produce as many papers as possible.</p> +<p>In engineering, the devil is in the detail. You really need to get into the weeds before you can know what the right thing to do is. This is simultaneously a devastating situation for industry and academia. Nobody is going to wait around for a team of engineers to finish building a system to write about it in Academia. You’ll be passed immediately by legions of paper pushers. And in industry, you can’t just be mucking about with a system that you might have to throw away.</p></blockquote> \ No newline at end of file diff --git a/tags/semantic-web/page/1/index.html b/tags/semantic-web/page/1/index.html new file mode 100644 index 0000000..740a44f --- /dev/null +++ b/tags/semantic-web/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/semantic-web/ + \ No newline at end of file diff --git a/tags/snippet/index.html b/tags/snippet/index.html new file mode 100644 index 0000000..57de5ed --- /dev/null +++ b/tags/snippet/index.html @@ -0,0 +1,33 @@ +Tag: Snippet - J. Fernando Sánchez +

Tags

2 pages

Snippet

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/snippet/index.xml b/tags/snippet/index.xml new file mode 100644 index 0000000..897c74f --- /dev/null +++ b/tags/snippet/index.xml @@ -0,0 +1,28 @@ +Snippet on J. Fernando Sánchezhttps://balkian.com/tags/snippet/Recent content in Snippet on J. Fernando SánchezHugo -- gohugo.ioen-usEmacs: show plain text versionhttps://balkian.com/p/emacs-show-plain-text-version/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/emacs-show-plain-text-version/<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-lisp" data-lang="lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">font-lock-mode</span><span class="p">)</span> +</span></span></code></pre></td></tr></table> +</div> +</div>Fixing HDMI flickeringhttps://balkian.com/p/fixing-hdmi-flickering/Mon, 01 Jan 0001 00:00:00 +0000https://balkian.com/p/fixing-hdmi-flickering/<img src="https://balkian.com/img/rpi.png" alt="Featured image of post Fixing HDMI flickering" /><p>Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_drive</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_group</span><span class="o">=</span><span class="mi">2</span> +</span></span><span class="line"><span class="cl"> <span class="n">hdmi_mode</span><span class="o">=</span><span class="mi">42</span> +</span></span><span class="line"><span class="cl"> <span class="n">disable_overscan</span><span class="o">=</span><span class="mi">1</span> +</span></span><span class="line"><span class="cl"> <span class="n">config_hdmi_boost</span><span class="o">=</span><span class="mi">7</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/snippet/page/1/index.html b/tags/snippet/page/1/index.html new file mode 100644 index 0000000..81140bd --- /dev/null +++ b/tags/snippet/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/snippet/ + \ No newline at end of file diff --git a/tags/starters/index.html b/tags/starters/index.html new file mode 100644 index 0000000..a7d417e --- /dev/null +++ b/tags/starters/index.html @@ -0,0 +1,33 @@ +Tag: Starters - J. Fernando Sánchez +

Tags

1 page

Starters

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/starters/index.xml b/tags/starters/index.xml new file mode 100644 index 0000000..e795c4d --- /dev/null +++ b/tags/starters/index.xml @@ -0,0 +1,48 @@ +Starters on J. Fernando Sánchezhttps://balkian.com/tags/starters/Recent content in Starters on J. Fernando SánchezHugo -- gohugo.ioen-usThu, 22 Aug 2013 14:14:22 +0000Creating my webhttps://balkian.com/p/creating-my-web/Thu, 22 Aug 2013 14:14:22 +0000https://balkian.com/p/creating-my-web/<p>I&rsquo;ve finally decided to set up a decent personal page. I have settled +for github-pages because I like the idea of keeping my site in a +repository and having someone else host and deploy it for me. The site +will be really simple, mostly static files. Thanks to Github, +<a class="link" href="http://jekyllrb.com" target="_blank" rel="noopener" +>Jekyll</a> will automatically generate static pages +for my posts every time I commit anything new to this repository.</p> +<p>But Jekyll can be used independently, so if I ever choose to host the +site myself, I can do it quite easily. Another thing that I liked about +this approach is that the generated html files can be used in the +future, and I will not need Jekyll to serve it. Jekyll is really simple +and most of the things are written in plain html. That means that +everything could be easily reused if I ever choose to change to another +blogging framework (e.g. pelical). But, for the time being, I like the +fact that Github takes care of the compilation as well, so I can simply +modify or add files through the web interface should I need to.</p> +<p>I hadn&rsquo;t played with HTML and CSS for a while now, so I also wanted to +use this site as a playground. At some point, I realised I was doing +mostly everything in plain HTML and CSS, and decided to keep it like +that for as long as possible. As of this writing, I haven&rsquo;t included +any Javascript code in the page. Probably I will use some to add my +<a class="link" href="http://gist.github.com/balkian" target="_blank" rel="noopener" +>gists</a> and +<a class="link" href="http://github.com/balkian" target="_blank" rel="noopener" +>repositories</a>, but we will see about that.</p> +<p>I think the code speaks for itself, so you can check out <a class="link" href="http://github.com/balkian/balkian.github.com" target="_blank" rel="noopener" +>my repository +on Github</a>. You can clone +and deploy it easily like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone +</span></span><span class="line"><span class="cl">https://github.com/balkian/balkian.github.com <span class="nb">cd</span> balkian.github.com +</span></span><span class="line"><span class="cl">jekyll serve -w +</span></span></code></pre></td></tr></table> +</div> +</div><p>I will keep updating this post with information about:</p> +<ul> +<li>Some Jekyll plugins that might be useful</li> +<li>What CSS tricks I learnt</li> +<li>The webfonts I used</li> +<li>The badge on the left side of the page</li> +</ul> \ No newline at end of file diff --git a/tags/starters/page/1/index.html b/tags/starters/page/1/index.html new file mode 100644 index 0000000..2e9c177 --- /dev/null +++ b/tags/starters/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/starters/ + \ No newline at end of file diff --git a/tags/surface-go/index.html b/tags/surface-go/index.html new file mode 100644 index 0000000..9214bf0 --- /dev/null +++ b/tags/surface-go/index.html @@ -0,0 +1,33 @@ +Tag: Surface Go - J. Fernando Sánchez +

Tags

1 page

Surface Go

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/surface-go/index.xml b/tags/surface-go/index.xml new file mode 100644 index 0000000..8103da2 --- /dev/null +++ b/tags/surface-go/index.xml @@ -0,0 +1,80 @@ +Surface Go on J. Fernando Sánchezhttps://balkian.com/tags/surface-go/Recent content in Surface Go on J. Fernando SánchezHugo -- gohugo.ioen-usSat, 01 Jun 2019 00:00:01 +0000Linux on the Microsoft Surface Gohttps://balkian.com/p/linux-on-the-microsoft-surface-go/Sat, 01 Jun 2019 00:00:01 +0000https://balkian.com/p/linux-on-the-microsoft-surface-go/<p>Believe it or not, Surface tablets have pretty good linux support, except for the webcams in newer models. +These are some useful notes to get Ubuntu installed in your surface go, as of Summer 2019.</p> +<h2 id="installing-the-kernel">Installing the kernel +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone --depth <span class="m">1</span> https://github.com/jakeday/linux-surface.git ~/linux-surface +</span></span><span class="line"><span class="cl">cp -a ~/linux-surface /media/&lt;your usb&gt; +</span></span></code></pre></td></tr></table> +</div> +</div><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -a /media/&lt;your usb&gt;/linux-surface ~/ +</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/linux-surface/ +</span></span><span class="line"><span class="cl">sudo sh setup.sh +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="booting-ubuntu-first">Booting ubuntu first +</h2><p>Switch out of Windows S mode.</p> +<p>Boot into the &ldquo;Command Prompt&rdquo;.</p> +<p>From Windows go to &ldquo;change advanced startup options&rdquo; and select &ldquo;restart now&rdquo;.</p> +<p>When it reboots, choose the &ldquo;Troubleshoot&rdquo; option, then choose the &ldquo;Advanced options&rdquo; option, and finally choose the &ldquo;Command Prompt&rdquo; option.</p> +<p>After the device reboots, login to the command prompt and then you should see a terminal with X:\windows\system32&gt;</p> +<p>At the prompt, check your UEFI entries:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /enum firmware +</span></span></code></pre></td></tr></table> +</div> +</div><p>Copy UEFI entry of &ldquo;Windows Boot Manager&rdquo; to create a new entry for Ubuntu: bcdedit /copy {bootmgr} /d &ldquo;Ubuntu&rdquo;</p> +<p>Copy the printed GUID number including the braces {} using Ctrl+C</p> +<p>Set file path for the new Ubuntu entry. Replace {guid} with the returned GUID of the previous command (Ctrl+V). bcdedit /set {guid} path \EFI\ubuntu\grubx64.efi</p> +<p>Set Ubuntu as the first/ entry in the boot sequence. Again replace {guid} with the returned GUID of the copy command.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bcdedit /set <span class="o">{</span>fwbootmgr<span class="o">}</span> displayorder <span class="o">{</span>guid<span class="o">}</span> /addfirst +</span></span></code></pre></td></tr></table> +</div> +</div><p>Check your UEFI entries again: bcdedit /enum firmware You should see something like this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl">Firmware Boot Manager +</span></span><span class="line"><span class="cl">--------------------- +</span></span><span class="line"><span class="cl">identifier <span class="o">{</span>fwbootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl">displayorder <span class="o">{</span>3510232e-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>bootmgr<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>2148799b-f8eb-e811-95ce-9ecab3f9d1c4<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a67-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl"> <span class="o">{</span>312e8a68-c2f6-e811-95ce-3c1ab3f9d1de<span class="o">}</span> +</span></span><span class="line"><span class="cl">timeout <span class="m">0</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Make sure the GUID you copied is the first one listed in displayorder. Then type exit, turn off the PC and turn it back on. After this my surface go is automatically booting to the grub bootloader which lets me choose between Windows and Ubuntu but defaults to Ubuntu after ten seconds.</p> \ No newline at end of file diff --git a/tags/surface-go/page/1/index.html b/tags/surface-go/page/1/index.html new file mode 100644 index 0000000..cf077cf --- /dev/null +++ b/tags/surface-go/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/surface-go/ + \ No newline at end of file diff --git a/tags/team/index.html b/tags/team/index.html new file mode 100644 index 0000000..7a3af4b --- /dev/null +++ b/tags/team/index.html @@ -0,0 +1,33 @@ +Tag: Team - J. Fernando Sánchez +

Tags

1 page

Team

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/team/index.xml b/tags/team/index.xml new file mode 100644 index 0000000..645352d --- /dev/null +++ b/tags/team/index.xml @@ -0,0 +1,327 @@ +Team on J. Fernando Sánchezhttps://balkian.com/tags/team/Recent content in Team on J. Fernando SánchezHugo -- gohugo.ioen-usWed, 05 Mar 2025 09:25:54 +0100Tips for efficient collaborationhttps://balkian.com/p/efficient-collaboration/Wed, 05 Mar 2025 09:25:54 +0100https://balkian.com/p/efficient-collaboration/<h2 id="background">Background +</h2><blockquote class="note"><p>TL;DR I work in academia. This post focuses on advice I&rsquo;d give a younger me to be a more effective supervisor and project lead.</p></blockquote> +<p>My role in my research group has evolved from individual contributor to project lead that manages a team of multiple students. +This often involves coordinatating with other senior researchers and their teams.</p> +<p>This post is a collection of advice I would have given myself back when I started this journey. +It is also an excuse to reflect on these ideas I&rsquo;ve been implicitly applying everyday, and maybe learn a few things more in the process.</p> +<p>In my field, projects are often tied to a specific grant or some sort of public funding. +This means that the main concern of the lead is to ensure that the results at the end of the project match the description in the project proposal. +It also typically means maximizing the number of publications related to the project and their overall impact.</p> +<p>To do so, most projects rely on three types of staff: a) senior researchers (post-doc); b) junior researchers (PhD students); and c) interns doing their bachelor&rsquo;s or master&rsquo;s thesis. +The level of contribution is generally inversely propotional to the level of mastery of the contributor: PhD students design and develop the main contributions (both software and experimental) under the supervision of senior researchers (advisor/supervisor), and interns take care of tasks that are narrow in scope and not crucial to any academic contribution. +For instance, a PhD student may develop a new model for text classification, and an intern will wrap that model in an HTTP service with a nice UI. +When the service and UI part is intricate and has some potential academic merit, that task may be conducted by a PhD student as part of their thesis. +That was precisely the case with Senpy, which was part of my PhD thesis, and it has since been used by dozens of students to develop services in the context of other research projects.</p> +<h2 id="reasons-to-form-a-team">Reasons to form a team +</h2><p>In my opinion, a team has two advantages over a single contributor. +The first one is that collaboration often generates <strong>synergies</strong>, leading to surprising and enriching results (a team is greater than the sum of its parts) +Carefully selecting your team members and creating an environment that is conducing to these synergies is a topic on its own, and I will not get too deep into it here.</p> +<p>The second advantage can be summarized as <strong>concurrency</strong>: tasks can be tackled by more than one member. +This often implies some sort of parallelism. +Tasks tend to be split between different members, in hopes of speeding up the process. +But it can also be beneficial to have concurrency even in the absence of parallelism: different members can take turns solving the same problem. +This is common when the task requires exclusive access to a resource (e.g., performing an experiment on an expensive machine).</p> +<h2 id="challenges-of-teams-of-students">Challenges of teams (of students) +</h2><p>Just like in computer science, coordination in a concurrent project involves a non-negligible overhead. +The objective is to minimize this overhead.</p> +<p>I&rsquo;ve really struggled with managing teams in the past. +Given my context, I often attributed the failures to lack of time (having to juggle teaching and research), lack of training and preparation on the intern&rsquo;s side (they&rsquo;re often undergrads), lack of appeal or definition of the task (too academic-y), or any other external factors.</p> +<p>While all those aspects play an important role, some of them are out of our control as a project lead in academia: grants require a certain type of project and workflow, and the quality of our interns is bounded by the quality of our degrees. +So I think it is more constructive to focus on things that we can control. +In other words: we have to play the hand we&rsquo;re given.</p> +<p>Besides, there is no merit in achieving good results with excellent students/engineers. +They would succeed on their own even if you weren&rsquo;t there. +The real test for a good leader is succeeding with a subpar team.</p> +<p>In that vein, I&rsquo;ve reflected on my mistakes as a leader, and the inefficiencies of the teams around me. +I&rsquo;d classify my failures in the following areas:</p> +<ul> +<li>Delegation (and lack thereof). Piling up too many tasks and blocking progress.</li> +<li>Communication. Not having a coherent view of the state of affairs, the details of specific tasks, the general processes to follow, or the priorities of different tasks within a project.</li> +<li>Direction (or purpose). Not having a common direction</li> +</ul> +<h3 id="delegation">Delegation +</h3><p>I have a tendency to become a bottleneck in any project I am involved in: many tasks end up depending on me, either directly or indirectly. In the concurrency metaphor, I become a lock for many tasks, and a single executor for the rest. +This issue was not that apparent early on, when most of my work was as an individual contributor or I had the bandwidth to supervise and complete my tasks.</p> +<p>I think it is quite common to feel like delegating a task in these scenarios means:</p> +<ol> +<li>Defining the task in advance</li> +<li>Choosing an assignee for the task</li> +<li>Setting a deadline for the task</li> +<li>Explaining the task and the relevant context</li> +<li>Replying to multiple questions by e-mail or in person. Extra points when the questions make you wonder if any part of the explanation was ever clear.</li> +<li>Reviewing the results after the deadline</li> +<li>Realizing the assignee misunderstood the task or delivered something not even close to what you agreed upon</li> +<li>Going back to point 3.</li> +<li>When you&rsquo;re unlucky or short on time: giving up and doing the task yourself</li> +</ol> +<p>Many times, if felt like delegating tasks only lead to frustration and wasted time. +Especially when compared to the alternative:</p> +<ul> +<li>Defining the task</li> +<li>Setting a deadline</li> +<li>Finishing the task</li> +<li>Profit</li> +</ul> +<p>Luckily, some students and projects were an exception to this. +They worked autonomously and delivered something beyond the minimum requirements. +This reinforced my helplessness and the feeling that the problem was not being able to work with experienced engineers.</p> +<p>However, I now believe that the truth lies somewhere in between. +Sometimes your circumstances make it quite hard or inefficient to delegate tasks. +And some times may not be good candidates for delegation. +But most of the times you can take advantage of having an extra pair of hands, you just have to do that effectively.</p> +<h3 id="communication">Communication +</h3><p>Small teams rely on implicit knowledge more than they realize. +Even more so if the team is made up of highly specialized people that have worked in the same environment with mostly the same people for years.</p> +<p>Communication is a broad term. +It includes technical and concrete things such as how a certain task should be done. +But it also includes broader things like etiquette, organizational values, and who is more willing to help you out on certain topics on a Friday afternoon.</p> +<p>Here, I would take a page out of Python&rsquo;s zen and recommend that &ldquo;explicit is better than implicit&rdquo;. +Implicit (or tacit) knowledge comes with a whole set of drawbacks:</p> +<ul> +<li>It makes onboarding new users harder. Without a common knowledge base, all knowledge transference has to rely on personal interactions. Even worse, those interactions are probably organized on the spot, and are likely to miss important points.</li> +<li>It makes you heavily reliant on your current members (and their memory).</li> +<li>It impedes proper evaluationn and progress, since they are not written anywhere.</li> +<li>It increases the likelihood of misunderstandings when two members have conflicting beliefs, and makes it harder to detect them until it is too late.</li> +<li>It makes contradictions and (unknowingly) changing your mind much more likely. It can happen to the best of us, especially if you are involved in too many projects. When contraditions happen often, your colleagues will learn not to rely on your opinion.</li> +</ul> +<p>On the other hand, communication has to go both ways. +This means that your newer members need to be able to communicate when something is going wrong or can be improved (backpressure). +They should also feel free to talk about their motivation, state of mind, and feelings, <strong>when appropriate</strong>. +That last part is quite subjective, of course. +Try to find your - and your organization&rsquo;s - middle ground between &ldquo;I don&rsquo;t care how you feel, just do your job&rdquo; and &ldquo;sure, you can go to the Maldives on short notice. Oh, and don&rsquo;t worry about not having met a deadline in months, I&rsquo;m sure you&rsquo;re stressed and can use some vacation but will work remotely if we need you&rdquo;.</p> +<p>I personally feel a sweet spot is treating your coworkers like people, being empathetic and compassionate. +Part of being a good coworker is fulfilling the duties and obligations you accepted when signing your contract. +First and foremost, because not fulfilling them means someone else will have to work harder to make up for it. +And, secondly, because doing our part is the only way to move the organization (and research) forward.</p> +<h3 id="direction">Direction +</h3><p>By failure in direction I mean not keeping a consistent and shared set of general goals, principles and values in your organization. +In order to really take part in any enterprise, you need to have a clear understanding of the objectives and motivation. +When it comes to specific tasks, <em>what you&rsquo;re doing</em> is often not as important as the <em>why you&rsquo;re doing it</em>. +In fact, there may be times where you aren&rsquo;t truly sure of what exactly it is that you are doing, but you trust the process and the motivation behind the task.</p> +<p>I&rsquo;ve seen two failure modes in this regard. +The first one is to not have a clear direction. +The end result is that members of the team are not really that committed. +If no other <em>why</em> is provided, we are only left with <em>because they pay me to do it</em>. +And academia is not known to pay particularly well, to be honest.</p> +<p>The other mode is to provide contradicting or incompatible directions. +This can be in a short period of time, leading to the impression that there isn&rsquo;t really any conviction in the message. +But it can also be done over a longer period of time. +That can be perfectly acceptable, provided that the change in direction is justified and compatible with the principles of the organization.</p> +<p>Failure in direction is somewhat related to communication, but it is subtly different. +An organization can excel at communication, but change their direction constantly. +Arguably, a thorough communication strategy makes radical changes in direction less likely. +On the one hand, a change in direction needs to be documented, which can be a pain. +On the other hand, a written change is easier to spot and more likely to generate complaints.</p> +<h2 id="rules">Rules +</h2><p>I&rsquo;d argue that the path to successfully managing a research team lies in roughly the following key goals:</p> +<ul> +<li>Fostering autonomy</li> +<li>Avoiding miscommunication</li> +<li>Optimizing your contribution</li> +</ul> +<p>The remaining of the post will be a series of tasks or rules to achieve these goals.</p> +<blockquote class="warning"><p>Most of these ideas probably generalize well to collaboration outside of academia, but I hesitate to make more general claims.</p></blockquote> +<h3 id="fostering-automony">Fostering automony +</h3><p>The tips here are aimed at avoiding supervision overhead and training future leads.</p> +<h4 id="provide-a-simplified-version-of-the-bigger-picture">Provide a (simplified version of the) bigger picture +</h4><p>Try to paint the bigger picture, even for menial tasks within large projects. +For you, this may be the nth project you&rsquo;re involved in this year, but the new intern may not have even heard about European projects before. +Going back to the idea of direction, it is easier to work on something if you know the context of your work.</p> +<p>Having a general idea of the project and the context of your task will also help you make decisions on your own. +For instance, if I am told to develop a shiny new API for text classification, I may have to ask many questions: 1) what will be input look like?; 2) what should the parameters be?; 3) am I using POST or GET requests?; 4) should I return a JSON object or an XML?&hellip; +What if, instead of that, I am also told this API will be used in the context of project X, that our organization will be the only consumers of the API, and they also give me a link to the project&rsquo;s docs. +I may be able to figure out some of those answers on my own (e.g., by finding examples in the project&rsquo;s website), or decide that some questions are not vital at this point (e.g., if we are the only consumers, we can change from GET to POST if we need to much more easily).</p> +<p>One caveat here is that a link to the documentation or some vague words about the project do not constitute proper context. +You are responsible for summarizing the important bits of the context, providing instructions on how to navigate the reference materials, and being open to answer questions that may arise in the exchange.</p> +<h4 id="do-not-discuss-implementation-details-unless-strictly-necessary">Do not discuss implementation details unless strictly necessary +</h4><p>There is a fine line between discussing a non-trivial implementation detail and bikeshedding for hours about class names and code best practices. +For that reason, you should try to prioritize discussing high-level parts of the problem and the assignment, and trust the student to figure out the details on their own, or come back to you for clarification.</p> +<p>It is very common that students focus on very specific details when they are sharing their progress with their supervisors. +They will generally try to start by showing snippets of code and their results. +I find it helpful to remind them to explain their problems top-to-bottom, starting with a sentence or two about the context of their project, the description and motivation of the specific task they were doing, and the relationship with previous (and future) tasks. +That usually helps figure out the level of understanding of the student, whether there are any conceptual errors, and whether the specific block or problem is really worth discussing during the meeting.</p> +<p>Some technical problems will warrant a discussion in detail, either due to their complexity or their importance to the project. +In those cases, always limit the time you will spend on that specific issue ahead of time, and make sure to allow for some time at the end of the meeting to go back to any important high-level details.</p> +<p>If there are other students that worked on similar projects, do not hesitate to refer your new student to them. +It can be an opportunity for them to collaborate, and for the original student to work on explaining and teaching technical issues.</p> +<h4 id="provide-feedback">Provide feedback +</h4><p>Make a point of evaluating the results of each student on every level, and provide constructive and actionable feedback to them. +Even if no technical issues arise during the project, try to review the code and give some tips (e.g., formatting, code structure, DRY). +Try to focus on bigger issues and enforcing best practices before nitpicking and giving feedback on small subjective improvements.</p> +<p>Make it clear when your feedback is objective/best practice (e.g., a function is deprecated) and when it is a matter of preference. +If it is the latter, try to provide more than one alternative, to encourage them to think about it and make an educated decision.</p> +<h4 id="take-documentation-and-knowledge-transfer-seriously">Take documentation and knowledge transfer seriously +</h4><p>Taking the time to write down basic documentation can save a lot of time in the long run. +Besides, most of the job of mentoring a new student is lost when that student finishes their degree and leaves to find a job in industry. +Good documentation can remain in your organization and be extended long after the intern is gone.</p> +<p>This is very obvious for specific tools, whether internal or public. +Good documentation means any new member can check the tool and use it without much assistance. +Even better documentation helps newcomers contribute to the tool. +Make sure to make it clear who to approach if something is missing from the documentation, and make it easier to do so than to make assumptions and use the tool incorrectly.</p> +<p>Writing documentation can be very time consuming, and sometimes it is hard to know exactly what things to focus on when writing the docs. +You need to anticipate the needs of the future user. +If you are short on time, a good strategy is to delegate the writing of the documentation. +Instead of going into details, you can write a very barebones version and training a new user to use and contribute to your tool. +Then, leave it up to the new user to extend the documentation, including more details and pitfalls. +As a bonus, reading and fixing the docs will give you a better sense of how well that new user understands the tool, as well as possible improvements.</p> +<p>This tip also applies to more general areas such as machine learning, graph neural networks, or simulation. +Just remember you do not need to reinvent the wheel in those cases. +A simple summary and a list of references to expand on the topic could be more than enough. +Make sure to also include any specifics that apply to your organization. +For instance, point to repositories on github (public or private) that can be used to explore the topic, examples of similar projects in the domain, etc.</p> +<p>Identify what information is important for any new hire and present it to them as clearly as possible. +Part of that information should be where and how common knowledge is stored and shared, should they need more information in the future. +Make this documentation as easy to discover and consume as possible. +Centralizing this common information in the form of a wiki is often a good idea.</p> +<p>Lastly, make it easy for any member of your organization to update this common documentation, and encourage them to do so. +Whenever a member asks you something useful that is not documented, don&rsquo;t just answer the question. +Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer. +If your organization&rsquo;s culture does not encourage using these docs, they will quickly get outdated and fall out of use.</p> +<p>One example of taking this documentation approach really seriously is <a class="link" href="https://oxide.computer/" target="_blank" rel="noopener" +>Oxide (computer company)</a>. +They have a process they call <a class="link" href="https://rfd.shared.oxide.computer/" target="_blank" rel="noopener" +>Request For Discussion (RFD)</a>, which they use to discuss and document both technical and organizational decisions. +For instance, they have <a class="link" href="https://rfd.shared.oxide.computer/rfd/0537" target="_blank" rel="noopener" +>RFDs on why they record every meeting</a>, <a class="link" href="https://rfd.shared.oxide.computer/rfd/0110" target="_blank" rel="noopener" +>RFDs about their choice of database</a>, and even <a class="link" href="https://rfd.shared.oxide.computer/rfd/0001" target="_blank" rel="noopener" +>an meta-RFD that discusses the motivation RFDs and how the process should work</a>.</p> +<h4 id="trust-your-teammates-ability-to-learn">Trust your teammate&rsquo;s ability to learn +</h4><p>I&rsquo;ve been bitten by this way too many times. +Your students are probably more capable of learning than you think, especially if you have set up your documentation right. +What they lack in experience, they make up for with free time, a (more) neuroplasticity and determination.</p> +<p>Sure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.</p> +<h4 id="use-tools-wisely">Use tools wisely +</h4><p>Your students probably have little experience with code versioning, reviewing processes, time management, etc. +A good choice of tools and some training can go a long way and make your life much easier in the long run. +It will also give your students a taste of what working in a bigger/real company feels like and a head start.</p> +<p>For instance, using git makes it easier to collaborate on code. +It also ensures that your results will not be lost if your student&rsquo;s laptop gets stolen.</p> +<p>Using GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students. +It will force them to commit working code, and it will make it easier to check their results and discuss the end result.</p> +<p>Using overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results. +You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration. +In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.</p> +<p>Also, on a related note, make sure every team member has a proper development setup. +It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work. +It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.</p> +<h4 id="encourage-cooperation">Encourage cooperation +</h4><p>Do not become the center of every conversation. +If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.</p> +<p>The ability to discuss with your peers and report only when needed will be extremely important for them in the future. +They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are). +That might lead to valuable insights and improvements for your team and project.</p> +<p>Moreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.</p> +<h4 id="reward-proactivity">Reward proactivity +</h4><p>The whole point of this section is to get your team to work independently when possible. +Be explicit about this goal to make sure it is clear to everyone. +And encourage behavior that aligns with this goal, even on a small scale.</p> +<p>For instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements. +Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time. +Do not jump straight to criticize it. +Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.</p> +<h4 id="dont-be-a-perfectionist">Don&rsquo;t be a perfectionist +</h4><p>Perfect is the enemy of done. +It is also the enemy of a happy co-worker.</p> +<p>Try to remember that you are dealing with students, and you were probably no better at their age. +Besides, you probably delegated the task beause you did not have any spare time to do it yourself. +<code>FIXME</code> is often better than <code>TODO</code>.</p> +<p>Take the opportunity to provide some feedback and teach them something useful. +Some mistakes are also worth adding to your documentation, or presenting to other students in a presentation.</p> +<h3 id="avoiding-miscommunication">Avoiding miscommunication +</h3><p>A common source of wasted effort and unnecessary back-and-forth is miscommunication. +These are some tips to help keep everyone on the team informed and aligned.</p> +<h4 id="make-priorities-clear">Make priorities clear +</h4><p>All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks. +This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.</p> +<h4 id="define-boundaries-and-abstractions">Define boundaries (and abstractions) +</h4><p>Once again, the goal is generally to achieve some sort of parallelism between your team members. +In order to do that, they need to know how they will interact with each other.</p> +<p>On a more general level, this means knowing the responsibilities and scope of your work.</p> +<p>On a more specific level, it means knowing their dependency graph. +In other words, whether the progress of one team member will depend on the results of another one. +Whenever there is a dependency, the interface should be made very clear. +This often takes the form of an API, a file with a given format, or a section of a document.</p> +<p>Take some time to define the boundary as precisely as needed at that point in the project. +I would suggest having specific examples that you can discuss and modify. +It is hard to discuss in the abstract, especially for inexperienced contributors. +When in doubt, default to the simplest option (e.g., a common file vs using a database). +Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there. +Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.</p> +<p>One type of failure I&rsquo;ve seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples. +That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.</p> +<h4 id="be-approachable">Be approachable +</h4><p>Did I wrote a whole section about autonomy? Yes. +Is the end goal to do more and talk less? Also yes. +Thing is, no process is perfect, and misunderstandings are bound to happen at some point. +If your only response to questions is a grumpy face or a &ldquo;read the freaking docs&rdquo;, your students will not alert you when something really needs your attention, and you will find out too late. +For instance, the documentation may be unclear, or your processes may be inadvertedly alienating new members and making new hires harder.</p> +<p>Another way to be approachable is to be clear about your shortcomings, and whether something you are saying is negotiable and/or debatable. +My rule of thumb is to err on the side of negotiable, and only be strict when it is really necessary (e.g., time constraints or an unproductive student). +We are all more likely to finish our tasks if we feel them ours, if we a say in how and when to perform them.</p> +<p>Just to be clear, approachable does not mean you have to be their confident or their best friend. +It also does not mean that it is okay to challenge or question you continuously. +Some times it is okay to simply say &ldquo;just do as I say&rdquo;.</p> +<h4 id="review-frequently">Review frequently +</h4><p>One type of review is individual. +It involves reviewing code on github, or reading deliverables and papers on overleaf. +It can help catch misunderstandings, and measure the true rate of progress in the individual tasks. +The other type of review is done as a group, by going through the key progress and action points. +This type of review helps everyone stay on the same page, and catch any general drifts in the project, such as misaligned priorities.</p> +<p>The frequency of each type of review depends on the specific nature of the project, the types of tasks being performed by the student, and your confidence on the student&rsquo;s abilities.</p> +<h3 id="optimizing-your-contribution">Optimizing your contribution +</h3><p>Tips on optimizing your contribution to the team.</p> +<h4 id="prioritize-prioritize-prioritize">Prioritize, prioritize, prioritize +</h4><p>Part of your job as a project lead is to identify the main goals in a project and to prioritize the tasks that will lead you there. +On the other hand, you are part of a research group, and you should be actively involved in its health and future. +Lastly, you are also in charge of the life-long project that is your research career.</p> +<p>In all these cases, your goal should be to identify the long term goals, come up with a sound strategy, and prioritize the tasks that will lead you and your group there. +Keeping your priorities straight will help you make steady progress, and avoid bikeshedding and changing goalposts. +It will also help you steer your progress in the right direction, since we all have limited time and effort and can&rsquo;t do everything at once.</p> +<p>The fact that your time is limited also means that you will need to decide how to prioritize these three roles. +I&rsquo;ve listed them in increasing level of importance for me. +It means that it is okay to focus on a specific project for a while, but if progress in your career is stalled - usually through publication - you need to reevaluate and concentrate your efforts on that.</p> +<h4 id="be-okay-with-short-term-inefficiencies">Be okay with (short-term) inefficiencies +</h4><p>I&rsquo;ve personally struggled with delegating tasks that will take me orders of magnitude less work than they will a student. +Thing is, most tasks will fall under this category, and your time is limited, so you have to delegate if you want to have time for more important matters. +If you never delegate any tasks, you are not allowing your team to learn and catch up on whatever technical skills are required. +Besides, you are not improving yourself on the managerial side of things. +It turns out delegating is hard, it requires a whole set of non-technical skills. +I suspect this is oftentimes the reason we don&rsquo;t do delegate in the first place: delegating is hard, and technical tasks are usually more straightforward, so we just don&rsquo;t want to do the work.</p> +<h4 id="dont-neglect-training">Don&rsquo;t neglect training +</h4><p>You are a senior researcher. +You probably know how to solve problems in your domain quite efficiently. +In my case, that means processing data and developing code.</p> +<p>That means I could dedicate my days to processing data and developing new code for my group. +That group would likely be used in multiple projects. +However, there is a hard limit to how much code I can push out in a day, especially if you take into account other obligations such as teaching.</p> +<p>A wiser strategy would be to set aside some of that coding time to instead help students become better programmers. +Firstly, because those students will be thankful and more motivated to work than when they are left to learn on their own without much guidance. +Secondly, because those students will then be more prepared to help me out if I delegate a task to them. +And, lastly, because these students have a whole life in fron of them. +A life full of big projects of their own, and contributions to society. +That little training time can have a compounding effect in the future.</p> +<h4 id="set-a-time-limit-for-your-interactions-in-advance">Set a time limit for your interactions in advance +</h4><p>Really long slots can easily lead to bikeshedding and going unnecessarily deep into implementation details, which is clearly an inefficient use of your time. +Even worse, our attention span and memory are finite, so longer and dense meetings can lead to fatigue and to missing or dilluting important points in the conversation.</p> +<p>For these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary. +You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.</p> +<h2 id="beyond-your-team">Beyond your team +</h2><p>The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control. +But teams rarely work in isolation, you will most likely +In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization&rsquo;s culture and sense of belonging.</p> +<p>Many of the aspects I talked about in the team section apply here. +For instance, the obsession with documentation can - and should - be applied organization-wise. +The same goes for defining boundaries and using concrete examples when collaborating with other teams. +For most intents and purposes, you can treat other teams as another contributor to your team. +Just one that will be more costly and slow to interact with.</p> +<p>If possible, I&rsquo;d try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved. +Avoid involving whole teams in discussions when the broad strokes have not been defined yet. +The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.</p> +<p>On the organization&rsquo;s side, I would suggest having an honest conversation about your core principles. +I really liked <a class="link" href="https://www.youtube.com/watch?v=9QMGAtxUlAchttps://www.youtube.com/watch?v=9QMGAtxUlAc" target="_blank" rel="noopener" +>Bryan Cantrill&rsquo;s talk about principles of technology leadership</a>. +He goes deep into the effects that principles have had on well known companies, and how to go about defining your company&rsquo;s principles. +I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.</p> +<p>More generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive. +And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier. +Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.</p> \ No newline at end of file diff --git a/tags/team/page/1/index.html b/tags/team/page/1/index.html new file mode 100644 index 0000000..bb5d071 --- /dev/null +++ b/tags/team/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/team/ + \ No newline at end of file diff --git a/tags/uwsgi/index.html b/tags/uwsgi/index.html new file mode 100644 index 0000000..1d943e6 --- /dev/null +++ b/tags/uwsgi/index.html @@ -0,0 +1,33 @@ +Tag: Uwsgi - J. Fernando Sánchez +

Tags

1 page

Uwsgi

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/uwsgi/index.xml b/tags/uwsgi/index.xml new file mode 100644 index 0000000..7effff4 --- /dev/null +++ b/tags/uwsgi/index.xml @@ -0,0 +1,105 @@ +Uwsgi on J. Fernando Sánchezhttps://balkian.com/tags/uwsgi/Recent content in Uwsgi on J. Fernando SánchezHugo -- gohugo.ioen-usThu, 09 Oct 2014 10:00:00 +0000Proxies with Apache and pythonhttps://balkian.com/p/proxies-with-apache-and-python/Thu, 09 Oct 2014 10:00:00 +0000https://balkian.com/p/proxies-with-apache-and-python/<p>This is a quick note on proxying a local python application (e.g. flask) +to a subdirectory in Apache. This assumes that the file wsgi.py contains +a WSGI application with the name <em>application</em>. Hence, wsgi:application.</p> +<h2 id="gunicorn">Gunicorn +</h2><div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPass</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">ProxyPassReverse</span> http://127.0.0.1:8888/myapp/ +</span></span><span class="line"><span class="cl"> <span class="nb">RequestHeader</span> set SCRIPT_NAME <span class="s2">&#34;/myapp/&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p><strong>Important</strong>: <em>SCRIPT_NAME</em> and the end of <em>ProxyPass</em> URL <strong>MUST BE +THE SAME</strong>. Otherwise, Gunicorn will fail miserably.</p> +<p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">venv/bin/gunicorn -w <span class="m">4</span> -b 127.0.0.1:8888 --log-file - --access-logfile - wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="uwsgi">UWSGI +</h2><p>This is a very simple configuration. I will try to upload one with more +options for uwsgi (in a .ini file).</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/myapp/</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">SetHandler</span> uwsgi_handler +</span></span><span class="line"><span class="cl"> <span class="nb">uWSGISocker</span> <span class="m">127.0.0.1</span>:8888 +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Try it with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uwsgi --socket 127.0.0.1:8888 -w wsgi:application +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extra-supervisor">Extra: Supervisor +</h2><p>If everything went as expected, you can wrap your command in a +supervisor config file and let it handle the server for you.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[unix_http_server]</span> +</span></span><span class="line"><span class="cl"><span class="na">file</span><span class="o">=</span><span class="s">/tmp/myapp.sock ; path to your socket file</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisord]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisor.log</span> +</span></span><span class="line"><span class="cl"><span class="na">childlogdir</span> <span class="o">=</span> <span class="s">%(here)s/logs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[rpcinterface:supervisor]</span> +</span></span><span class="line"><span class="cl"><span class="na">supervisor.rpcinterface_factory</span> <span class="o">=</span> <span class="s">supervisor.rpcinterface:make_main_rpcinterface</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[supervisorctl]</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/supervisorctl.log</span> +</span></span><span class="line"><span class="cl"><span class="na">serverurl</span><span class="o">=</span><span class="s">unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[program:myapp]</span> +</span></span><span class="line"><span class="cl"><span class="na">command</span> <span class="o">=</span> <span class="s">venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 --log-file %(here)s/logs/gunicorn.log --access-logfile - wsgi:application</span> +</span></span><span class="line"><span class="cl"><span class="na">directory</span> <span class="o">=</span> <span class="s">%(here)s</span> +</span></span><span class="line"><span class="cl"><span class="na">environment</span> <span class="o">=</span> <span class="s">PATH=%(here)s/venv/bin/</span> +</span></span><span class="line"><span class="cl"><span class="na">logfile</span> <span class="o">=</span> <span class="s">%(here)s/logs/myapp.log</span> +</span></span></code></pre></td></tr></table> +</div> +</div> \ No newline at end of file diff --git a/tags/uwsgi/page/1/index.html b/tags/uwsgi/page/1/index.html new file mode 100644 index 0000000..6740cae --- /dev/null +++ b/tags/uwsgi/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/uwsgi/ + \ No newline at end of file diff --git a/tags/webdav/index.html b/tags/webdav/index.html new file mode 100644 index 0000000..662de58 --- /dev/null +++ b/tags/webdav/index.html @@ -0,0 +1,33 @@ +Tag: Webdav - J. Fernando Sánchez +

Tags

1 page

Webdav

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/webdav/index.xml b/tags/webdav/index.xml new file mode 100644 index 0000000..57f6cac --- /dev/null +++ b/tags/webdav/index.xml @@ -0,0 +1,142 @@ +Webdav on J. Fernando Sánchezhttps://balkian.com/tags/webdav/Recent content in Webdav on J. Fernando SánchezHugo -- gohugo.ioen-usTue, 09 Dec 2014 12:12:12 +0000Zoterohttps://balkian.com/p/zotero/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/p/zotero/<p><a class="link" href="https://www.zotero.org/" target="_blank" rel="noopener" +>Zotero</a> is an Open Source tool that lets you +organise your bibliography, syncing it with the cloud. Unlike other +alternatives such as <a class="link" href="http://www.mendeley.com" target="_blank" rel="noopener" +>Mendeley</a>, Zotero can +upload the attachments and data to a private cloud via WebDav.</p> +<p>If you use nginx as your web server, know that even though it provides +partial support for webdav, Zotero needs more than that. Hence, you will +need another webdav server, and optionally let nginx proxy to it. This +short post provides the basics to get that set-up working under +Debian/Ubuntu.</p> +<h2 id="setting-up-apache">Setting up Apache +</h2><p>First we need to install Apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install apache2 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Change the head of &ldquo;/etc/apache2/sites-enabled/000-default&rdquo; to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;VirtualHost</span> <span class="s">*:880</span><span class="nt">&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Then, create a file /etc/apache2/sites-available/webdav:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nb">Alias</span> <span class="sx">/dav</span> <span class="sx">/home/webdav/dav</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/dav</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">on</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Order</span> Allow,Deny +</span></span><span class="line"><span class="cl"> <span class="nb">Allow</span> from <span class="k">all</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">On</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Options</span> +Indexes +</span></span><span class="line"><span class="cl"> <span class="nb">AuthType</span> Basic +</span></span><span class="line"><span class="cl"> <span class="nb">AuthName</span> DAV +</span></span><span class="line"><span class="cl"> <span class="nb">AuthBasicProvider</span> file +</span></span><span class="line"><span class="cl"> <span class="nb">AuthUserFile</span> <span class="sx">/home/webdav/.htpasswd</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Require</span> valid-user +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Ideally, you want your webdav folders to be private, adding +authentication to them. So you need to create the webdav and zotero +users and add the passwords to an htpasswd file. Even though you could +use a single user, since you will be configuring several clients with +your credentials I encourage you to create the zotero user as well. This +way you can always change the password for zotero without affecting any +other application using webdav.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo adduser webdav +</span></span><span class="line"><span class="cl">sudo htpasswd -c /home/webdav/.htpasswd webdav +</span></span><span class="line"><span class="cl">sudo htpasswd /home/webdav/.htpasswd zotero +</span></span><span class="line"><span class="cl">sudo mkdir -p /home/webdav/dav/zotero +</span></span></code></pre></td></tr></table> +</div> +</div><p>Enable the site and restart apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo a2enmod webdav +</span></span><span class="line"><span class="cl">sudo a2enmod dav_fs +</span></span><span class="line"><span class="cl">sudo a2ensite webdav +</span></span><span class="line"><span class="cl">sudo service apache2 restart +</span></span></code></pre></td></tr></table> +</div> +</div><p>At this point everything should be working at +<a class="link" href="http://" target="_blank" rel="noopener" +>http://</a>&lt;your_host&gt;:880/dav/zotero</p> +<h2 id="setting-up-nginx">Setting up NGINX +</h2><p>After the Apache side is working, we can use nginx as a proxy to get +cleaner URIs. In your desired site/location, add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/dav</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="kn">client_max_body_size</span> <span class="s">20M</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:880</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now just reload nginx:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo service nginx force-reload +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extras">Extras +</h2><ul> +<li><a class="link" href="http://zoteroreader.com/" target="_blank" rel="noopener" +>Zotero Reader</a> - HTML5 client</li> +<li><a class="link" href="https://github.com/ajlyon/zandy" target="_blank" rel="noopener" +>Zandy</a> - Android Open Source +client</li> +</ul> \ No newline at end of file diff --git a/tags/webdav/page/1/index.html b/tags/webdav/page/1/index.html new file mode 100644 index 0000000..ff73e76 --- /dev/null +++ b/tags/webdav/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/webdav/ + \ No newline at end of file diff --git a/tags/zigbee/index.html b/tags/zigbee/index.html new file mode 100644 index 0000000..73ace29 --- /dev/null +++ b/tags/zigbee/index.html @@ -0,0 +1,33 @@ +Tag: Zigbee - J. Fernando Sánchez +

Tags

1 page

Zigbee

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/zigbee/index.xml b/tags/zigbee/index.xml new file mode 100644 index 0000000..ea575e9 --- /dev/null +++ b/tags/zigbee/index.xml @@ -0,0 +1,132 @@ +Zigbee on J. Fernando Sánchezhttps://balkian.com/tags/zigbee/Recent content in Zigbee on J. Fernando SánchezHugo -- gohugo.ioen-usSun, 06 Jan 2019 10:00:00 +0000Controlling Zigbee devices with MQTThttps://balkian.com/p/controlling-zigbee-devices-with-mqtt/Sun, 06 Jan 2019 10:00:00 +0000https://balkian.com/p/controlling-zigbee-devices-with-mqtt/<p>This is a short tutorial on connecting a zigbee device (an Aqara cube) +to an MQTT server, so you can control your zigbee devices from the +network.</p> +<p>If you&rsquo;re anything like me, you&rsquo;re probably a sucker for IoT devices. +For a long time, I&rsquo;ve been using WiFi-enabled lights, and Amazon dash +buttons to control them. To keep these (cheap Chinese) internet enabled +devices away from your network and their respective cloud services, +you&rsquo;ll probably want to set up a dedicated network in your router (more +on this on a future post, maybe). Another disadvantage of WiFi devices +is that they&rsquo;re relatively power hungry.</p> +<p>A popular alternative is using ZigBee for communication. It is a +dedicated protocol similar to bluetooth (BLE), with lower power +requirements and bitrate.</p> +<p>Take the (super cute) aqara cube as an example. It is a small cube that +detects rotation on all of its axes, and tapping events. Here&rsquo;s a +video:</p> +<div class="video-wrapper"> +<iframe loading="lazy" +src="https://www.youtube.com/embed/5YtqG1wEnng" +allowfullscreen +title="YouTube Video" +> +</iframe> +</div> +<p>To connect to zigbee devices you will need a zigbee enabled gateway +(a.k.a. hub), which connects to your WiFi network and your zigbee +devices. Once again, this means adding an internet-enabled device to +your home, and probably a couple of cloud services.</p> +<p>As an alternative, you can set up your own zigbee gateway, and control +it to your home automation platform of choice (e.g. home assistant). We +will cover how to set up a zigbee2mqtt gateway that is also connected to +an MQTT server, so you can use MQTT to control your devices and get +notifications.</p> +<p>What you need:</p> +<ul> +<li><a class="link" href="https://www.aliexpress.com/item/Original-Xiaomi-Mi-Aqara-Cube-Smart-Home-Controller-6-Action-Operation-Fr-Home-Device-Zigbee-Version/32892947622.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>Aqara +cube</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/Wireless-Zigbee-CC2531-CC2540-Zigbee-Sniffer-Bluetooth-BLE-4-0-Dongle-Capture-Module-USB-Programmer-Downloader/32907587711.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC2531 zigbee +sniffer</a>.</li> +<li><a class="link" href="https://www.aliexpress.com/item/CFSUNBIRD-CC-DEBUGGER-Debugger-and-Programmer-for-RF-System-on-Chips-TI-ORIGINAL-Fast-hipping/32813122315.html?spm=a2g0s.9042311.0.0.3da24c4dXV8sBI" target="_blank" rel="noopener" +>CC-debugger</a>.</li> +</ul> +<p>You will need to flash your sniffer. For that, you only need to follow +the instructions from the <a class="link" href="https://koenkk.github.io/zigbee2mqtt/" target="_blank" rel="noopener" +>zigbee2mqtt +documentation</a>.</p> +<p>Once you&rsquo;re done flashing, you&rsquo;re ready to set up the zigbee2mqtt +server. For convenience, I wrote a simple docker-compose to deploy a +zigbee2mqtt server and a test mosquitto server:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span><span class="lnt">14 +</span><span class="lnt">15 +</span><span class="lnt">16 +</span><span class="lnt">17 +</span><span class="lnt">18 +</span><span class="lnt">19 +</span><span class="lnt">20 +</span><span class="lnt">21 +</span><span class="lnt">22 +</span><span class="lnt">23 +</span><span class="lnt">24 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;2.1&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">zigbee2mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">koenkk/zigbee2mqtt</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">zigbee2mqtt </span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./z2m-data/:/app/data/</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">devices</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="s2">&#34;/dev/ttyACM0&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">mqtt</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">eclipse-mosquitto</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">1883</span><span class="p">:</span><span class="m">1883</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="m">9001</span><span class="p">:</span><span class="m">9001</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">hass</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">volumes</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="l">./mosquitto.conf:/mosquitto/config/mosquitto.conf</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">networks</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hass</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">overlay</span><span class="w"> +</span></span></span></code></pre></td></tr></table> +</div> +</div><p>You can test your installation with:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">❯ mosquitto_sub -h localhost -p <span class="m">1883</span> -t <span class="s1">&#39;zigbee2mqtt/#&#39;</span> +</span></span><span class="line"><span class="cl">online +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:149,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;rotate_right&#34;</span>,<span class="s2">&#34;angle&#34;</span>:12.8<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;slide&#34;</span>,<span class="s2">&#34;side&#34;</span>:2<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:120<span class="o">}</span> +</span></span><span class="line"><span class="cl"><span class="o">{</span><span class="s2">&#34;battery&#34;</span>:17,<span class="s2">&#34;voltage&#34;</span>:2925,<span class="s2">&#34;linkquality&#34;</span>:141,<span class="s2">&#34;action&#34;</span>:<span class="s2">&#34;wakeup&#34;</span><span class="o">}</span></span></span></code></pre></td></tr></table> +</div> +</div> +<p>zigbee2mqtt supports the following events for the aqara cube: shake, +wakeup, fall, tap, slide, flip180, flip90, rotate_left and +rotate_right. Every event has additional information, such as the sides +involved, or the degrees turned.</p> +<p>Now you are ready to set up home assistant support in zigbee2mqtt +following <a class="link" href="https://koenkk.github.io/zigbee2mqtt/integration/home_assistant.html" target="_blank" rel="noopener" +>this +guide</a>.</p> \ No newline at end of file diff --git a/tags/zigbee/page/1/index.html b/tags/zigbee/page/1/index.html new file mode 100644 index 0000000..d869619 --- /dev/null +++ b/tags/zigbee/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/zigbee/ + \ No newline at end of file diff --git a/tags/zotero/index.html b/tags/zotero/index.html new file mode 100644 index 0000000..caf425d --- /dev/null +++ b/tags/zotero/index.html @@ -0,0 +1,33 @@ +Tag: Zotero - J. Fernando Sánchez +

Tags

1 page

Zotero

Built with Hugo
Theme Stack designed by Jimmy
\ No newline at end of file diff --git a/tags/zotero/index.xml b/tags/zotero/index.xml new file mode 100644 index 0000000..6478815 --- /dev/null +++ b/tags/zotero/index.xml @@ -0,0 +1,142 @@ +Zotero on J. Fernando Sánchezhttps://balkian.com/tags/zotero/Recent content in Zotero on J. Fernando SánchezHugo -- gohugo.ioen-usTue, 09 Dec 2014 12:12:12 +0000Zoterohttps://balkian.com/p/zotero/Tue, 09 Dec 2014 12:12:12 +0000https://balkian.com/p/zotero/<p><a class="link" href="https://www.zotero.org/" target="_blank" rel="noopener" +>Zotero</a> is an Open Source tool that lets you +organise your bibliography, syncing it with the cloud. Unlike other +alternatives such as <a class="link" href="http://www.mendeley.com" target="_blank" rel="noopener" +>Mendeley</a>, Zotero can +upload the attachments and data to a private cloud via WebDav.</p> +<p>If you use nginx as your web server, know that even though it provides +partial support for webdav, Zotero needs more than that. Hence, you will +need another webdav server, and optionally let nginx proxy to it. This +short post provides the basics to get that set-up working under +Debian/Ubuntu.</p> +<h2 id="setting-up-apache">Setting up Apache +</h2><p>First we need to install Apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get install apache2 +</span></span></code></pre></td></tr></table> +</div> +</div><p>Change the head of &ldquo;/etc/apache2/sites-enabled/000-default&rdquo; to:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nt">&lt;VirtualHost</span> <span class="s">*:880</span><span class="nt">&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Then, create a file /etc/apache2/sites-available/webdav:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt"> 1 +</span><span class="lnt"> 2 +</span><span class="lnt"> 3 +</span><span class="lnt"> 4 +</span><span class="lnt"> 5 +</span><span class="lnt"> 6 +</span><span class="lnt"> 7 +</span><span class="lnt"> 8 +</span><span class="lnt"> 9 +</span><span class="lnt">10 +</span><span class="lnt">11 +</span><span class="lnt">12 +</span><span class="lnt">13 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="nb">Alias</span> <span class="sx">/dav</span> <span class="sx">/home/webdav/dav</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;Location</span> <span class="s">/dav</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">on</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Order</span> Allow,Deny +</span></span><span class="line"><span class="cl"> <span class="nb">Allow</span> from <span class="k">all</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Dav</span> <span class="k">On</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Options</span> +Indexes +</span></span><span class="line"><span class="cl"> <span class="nb">AuthType</span> Basic +</span></span><span class="line"><span class="cl"> <span class="nb">AuthName</span> DAV +</span></span><span class="line"><span class="cl"> <span class="nb">AuthBasicProvider</span> file +</span></span><span class="line"><span class="cl"> <span class="nb">AuthUserFile</span> <span class="sx">/home/webdav/.htpasswd</span> +</span></span><span class="line"><span class="cl"> <span class="nb">Require</span> valid-user +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Location&gt;</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Ideally, you want your webdav folders to be private, adding +authentication to them. So you need to create the webdav and zotero +users and add the passwords to an htpasswd file. Even though you could +use a single user, since you will be configuring several clients with +your credentials I encourage you to create the zotero user as well. This +way you can always change the password for zotero without affecting any +other application using webdav.</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo adduser webdav +</span></span><span class="line"><span class="cl">sudo htpasswd -c /home/webdav/.htpasswd webdav +</span></span><span class="line"><span class="cl">sudo htpasswd /home/webdav/.htpasswd zotero +</span></span><span class="line"><span class="cl">sudo mkdir -p /home/webdav/dav/zotero +</span></span></code></pre></td></tr></table> +</div> +</div><p>Enable the site and restart apache:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo a2enmod webdav +</span></span><span class="line"><span class="cl">sudo a2enmod dav_fs +</span></span><span class="line"><span class="cl">sudo a2ensite webdav +</span></span><span class="line"><span class="cl">sudo service apache2 restart +</span></span></code></pre></td></tr></table> +</div> +</div><p>At this point everything should be working at +<a class="link" href="http://" target="_blank" rel="noopener" +>http://</a>&lt;your_host&gt;:880/dav/zotero</p> +<h2 id="setting-up-nginx">Setting up NGINX +</h2><p>After the Apache side is working, we can use nginx as a proxy to get +cleaner URIs. In your desired site/location, add this:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span><span class="lnt">2 +</span><span class="lnt">3 +</span><span class="lnt">4 +</span><span class="lnt">5 +</span><span class="lnt">6 +</span><span class="lnt">7 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/dav</span> <span class="p">{</span> +</span></span><span class="line"><span class="cl"> <span class="kn">client_max_body_size</span> <span class="s">20M</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$remote_addr</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"> <span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:880</span><span class="p">;</span> +</span></span><span class="line"><span class="cl"><span class="p">}</span> +</span></span></code></pre></td></tr></table> +</div> +</div><p>Now just reload nginx:</p> +<div class="highlight"><div class="chroma"> +<table class="lntable"><tr><td class="lntd"> +<pre tabindex="0" class="chroma"><code><span class="lnt">1 +</span></code></pre></td> +<td class="lntd"> +<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo service nginx force-reload +</span></span></code></pre></td></tr></table> +</div> +</div><h2 id="extras">Extras +</h2><ul> +<li><a class="link" href="http://zoteroreader.com/" target="_blank" rel="noopener" +>Zotero Reader</a> - HTML5 client</li> +<li><a class="link" href="https://github.com/ajlyon/zandy" target="_blank" rel="noopener" +>Zandy</a> - Android Open Source +client</li> +</ul> \ No newline at end of file diff --git a/tags/zotero/page/1/index.html b/tags/zotero/page/1/index.html new file mode 100644 index 0000000..3aebbdc --- /dev/null +++ b/tags/zotero/page/1/index.html @@ -0,0 +1,2 @@ +https://balkian.com/tags/zotero/ + \ No newline at end of file diff --git a/ts/main.1e9a3bafd846ced4c345d084b355fb8c7bae75701c338f8a1f8a82c780137826.js b/ts/main.1e9a3bafd846ced4c345d084b355fb8c7bae75701c338f8a1f8a82c780137826.js new file mode 100644 index 0000000..69ec30f --- /dev/null +++ b/ts/main.1e9a3bafd846ced4c345d084b355fb8c7bae75701c338f8a1f8a82c780137826.js @@ -0,0 +1,11 @@ +(()=>{var g=class e{galleryUID;items=[];constructor(t,r=1){if(window.PhotoSwipe==null||window.PhotoSwipeUI_Default==null){console.error("PhotoSwipe lib not loaded.");return}this.galleryUID=r,e.createGallery(t),this.loadItems(t),this.bindClick()}loadItems(t){this.items=[];let r=t.querySelectorAll("figure.gallery-image");for(let i of r){let n=i.querySelector("figcaption"),o=i.querySelector("img"),s={w:parseInt(o.getAttribute("width")),h:parseInt(o.getAttribute("height")),src:o.src,msrc:o.getAttribute("data-thumb")||o.src,el:i};n&&(s.title=n.innerHTML),this.items.push(s)}}static createGallery(t){let r=t.querySelectorAll("img.gallery-image");for(let o of Array.from(r)){let s=o.closest("p");if(!s||!t.contains(s)||(s.textContent.trim()==""&&s.classList.add("no-text"),!s.classList.contains("no-text")))continue;let d=o.parentElement.tagName=="A",m=o,c=document.createElement("figure");if(c.style.setProperty("flex-grow",o.getAttribute("data-flex-grow")||"1"),c.style.setProperty("flex-basis",o.getAttribute("data-flex-basis")||"0"),d&&(m=o.parentElement),m.parentElement.insertBefore(c,m),c.appendChild(m),o.hasAttribute("alt")){let l=document.createElement("figcaption");l.innerText=o.getAttribute("alt"),c.appendChild(l)}if(!d){c.className="gallery-image";let l=document.createElement("a");l.href=o.src,l.setAttribute("target","_blank"),o.parentNode.insertBefore(l,o),l.appendChild(o)}}let i=t.querySelectorAll("figure.gallery-image"),n=[];for(let o of i)n.length?o.previousElementSibling===n[n.length-1]?n.push(o):n.length&&(e.wrap(n),n=[o]):n=[o];n.length>0&&e.wrap(n)}static wrap(t){let r=document.createElement("div");r.className="gallery";let i=t[0].parentNode,n=t[0];i.insertBefore(r,n);for(let o of t)r.appendChild(o)}open(t){let r=document.querySelector(".pswp");new window.PhotoSwipe(r,window.PhotoSwipeUI_Default,this.items,{index:t,galleryUID:this.galleryUID,getThumbBoundsFn:n=>{let o=this.items[n].el.getElementsByTagName("img")[0],s=window.pageYOffset||document.documentElement.scrollTop,a=o.getBoundingClientRect();return{x:a.left,y:a.top+s,w:a.width}}}).init()}bindClick(){for(let[t,r]of this.items.entries())r.el.querySelector("a").addEventListener("click",n=>{n.preventDefault(),this.open(t)})}},b=g;var h={};if(localStorage.hasOwnProperty("StackColorsCache"))try{h=JSON.parse(localStorage.getItem("StackColorsCache"))}catch{h={}}async function S(e,t,r){if(!e)return await Vibrant.from(r).getPalette();if(!h.hasOwnProperty(e)||h[e].hash!==t){let i=await Vibrant.from(r).getPalette();h[e]={hash:t,Vibrant:{hex:i.Vibrant.hex,rgb:i.Vibrant.rgb,bodyTextColor:i.Vibrant.bodyTextColor},DarkMuted:{hex:i.DarkMuted.hex,rgb:i.DarkMuted.rgb,bodyTextColor:i.DarkMuted.bodyTextColor}},localStorage.setItem("StackColorsCache",JSON.stringify(h))}return h[e]}var D=(e,t=500)=>{e.classList.add("transiting"),e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=t+"ms",e.style.height=e.offsetHeight+"px",e.offsetHeight,e.style.overflow="hidden",e.style.height="0",e.style.paddingTop="0",e.style.paddingBottom="0",e.style.marginTop="0",e.style.marginBottom="0",window.setTimeout(()=>{e.classList.remove("show"),e.style.removeProperty("height"),e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property"),e.classList.remove("transiting")},t)},q=(e,t=500)=>{e.classList.add("transiting"),e.style.removeProperty("display"),e.classList.add("show");let r=e.offsetHeight;e.style.overflow="hidden",e.style.height="0",e.style.paddingTop="0",e.style.paddingBottom="0",e.style.marginTop="0",e.style.marginBottom="0",e.offsetHeight,e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=t+"ms",e.style.height=r+"px",e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom"),window.setTimeout(()=>{e.style.removeProperty("height"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property"),e.classList.remove("transiting")},t)},B=(e,t=500)=>window.getComputedStyle(e).display==="none"?q(e,t):D(e,t);function w(){let e=document.getElementById("toggle-menu");e&&e.addEventListener("click",()=>{document.getElementById("main-menu").classList.contains("transiting")||(document.body.classList.toggle("show-menu"),B(document.getElementById("main-menu"),300),e.classList.toggle("is-active"))})}function N(e,t,r){var i=document.createElement(e);for(let n in t)if(n&&t.hasOwnProperty(n)){let o=t[n];n=="dangerouslySetInnerHTML"?i.innerHTML=o.__html:o===!0?i.setAttribute(n,n):o!==!1&&o!=null&&i.setAttribute(n,o.toString())}for(let n=2;n{this.isDark()?this.currentScheme="light":this.currentScheme="dark",this.setBodyClass(),this.currentScheme==this.systemPreferScheme&&(this.currentScheme="auto"),this.saveScheme()})}isDark(){return this.currentScheme=="dark"||this.currentScheme=="auto"&&this.systemPreferScheme=="dark"}dispatchEvent(t){let r=new CustomEvent("onColorSchemeChange",{detail:t});window.dispatchEvent(r)}setBodyClass(){this.isDark()?document.documentElement.dataset.scheme="dark":document.documentElement.dataset.scheme="light",this.dispatchEvent(document.documentElement.dataset.scheme)}getSavedScheme(){let t=localStorage.getItem(this.localStorageKey);return t=="light"||t=="dark"||t=="auto"?t:"auto"}bindMatchMedia(){window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",t=>{t.matches?this.systemPreferScheme="dark":this.systemPreferScheme="light",this.setBodyClass()})}},E=y;function p(e){let t;return()=>{t&&window.cancelAnimationFrame(t),t=window.requestAnimationFrame(()=>e())}}var O=".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]",T="#TableOfContents",L="#TableOfContents li",k="active-class";function V(e,t){let r=e.querySelector("a").offsetHeight,i=e.offsetTop-t.offsetHeight/2+r/2-t.offsetTop;i<0&&(i=0),t.scrollTo({top:i,behavior:"smooth"})}function U(e){let t={};return e.forEach(r=>{let n=r.querySelector("a").getAttribute("href");n.startsWith("#")&&(t[n.slice(1)]=r)}),t}function C(e){let t=[];return e.forEach(r=>{t.push({id:r.id,offset:r.offsetTop})}),t.sort((r,i)=>r.offset-i.offset),t}function M(){let e=document.querySelectorAll(O);if(!e){console.warn("No header matched query",e);return}let t=document.querySelector(T);if(!t){console.warn("No toc matched query",T);return}let r=document.querySelectorAll(L);if(!r){console.warn("No navigation matched query",L);return}let i=C(e),n=!1;t.addEventListener("mouseenter",p(()=>n=!0)),t.addEventListener("mouseleave",p(()=>n=!1));let o,s=U(r);function a(){let m=document.documentElement.scrollTop||document.body.scrollTop,c;i.forEach(f=>{m>=f.offset-20&&(c=document.getElementById(f.id))});let l;c&&(l=s[c.id]),c&&!l?console.debug("No link found for section",c):l!==o&&(o&&o.classList.remove(k),l&&(l.classList.add(k),n||V(l,t)),o=l)}window.addEventListener("scroll",p(a));function d(){i=C(e),a()}window.addEventListener("resize",p(d))}var $="a[href]";function P(){document.querySelectorAll($).forEach(e=>{e.getAttribute("href").startsWith("#")&&e.addEventListener("click",r=>{r.preventDefault();let i=decodeURI(e.getAttribute("href").substring(1)),n=document.getElementById(i),o=n.getBoundingClientRect().top-document.documentElement.getBoundingClientRect().top;window.history.pushState({},"",e.getAttribute("href")),scrollTo({top:o,behavior:"smooth"})})})}var x={init:()=>{w();let e=document.querySelector(".article-content");e&&(new b(e),P(),M());let t=document.querySelector(".article-list--tile");t&&new IntersectionObserver(async(s,a)=>{s.forEach(d=>{if(!d.isIntersecting)return;a.unobserve(d.target),d.target.querySelectorAll("article.has-image").forEach(async c=>{let l=c.querySelector("img"),f=l.src,H=l.getAttribute("data-key"),I=l.getAttribute("data-hash"),A=c.querySelector(".article-details"),u=await S(H,I,f);A.style.background=` + linear-gradient(0deg, + rgba(${u.DarkMuted.rgb[0]}, ${u.DarkMuted.rgb[1]}, ${u.DarkMuted.rgb[2]}, 0.5) 0%, + rgba(${u.Vibrant.rgb[0]}, ${u.Vibrant.rgb[1]}, ${u.Vibrant.rgb[2]}, 0.75) 100%)`})})}).observe(t);let r=document.querySelectorAll(".article-content div.highlight"),i="Copy",n="Copied!";r.forEach(o=>{let s=document.createElement("button");s.innerHTML=i,s.classList.add("copyCodeButton"),o.appendChild(s);let a=o.querySelector("code[data-lang]");a&&s.addEventListener("click",()=>{navigator.clipboard.writeText(a.textContent).then(()=>{s.textContent=n,setTimeout(()=>{s.textContent=i},1e3)}).catch(d=>{alert(d),console.log("Something went wrong",d)})})}),new E(document.getElementById("dark-mode-toggle"))}};window.addEventListener("load",()=>{setTimeout(function(){x.init()},0)});window.Stack=x;window.createElement=v;})(); +/*! +* Hugo Theme Stack +* +* @author: Jimmy Cai +* @website: https://jimmycai.com +* @link: https://github.com/CaiJimmy/hugo-theme-stack +*/ diff --git a/ts/search.js b/ts/search.js new file mode 100644 index 0000000..51e5636 --- /dev/null +++ b/ts/search.js @@ -0,0 +1 @@ +(()=>{var m={"&":"&","<":"<",">":">",'"':""","\u2026":"…"};function T(l){return m[l]||l}function d(l){return l.replace(/[&<>"]/g,T)}function f(l){return l.replace(/[.*+\-?^${}()|[\]\\]/g,"\\$&")}var g=class l{data;form;input;list;resultTitle;resultTitleTemplate;constructor({form:t,input:e,list:r,resultTitle:o,resultTitleTemplate:n}){this.form=t,this.input=e,this.list=r,this.resultTitle=o,this.resultTitleTemplate=n,this.input.value.trim()!==""?this.doSearch(this.input.value.split(" ")):this.handleQueryString(),this.bindQueryStringChange(),this.bindSearchForm()}static processMatches(t,e,r=!0,o=140,n=20){e.sort((i,s)=>i.start-s.start);let h=0,a=0,c=0,u=[];for(;ha?(u.push(`${d(t.substring(a,a+n))} [...] `),u.push(`${d(t.substring(i.start-n,i.start))}`),c+=n*2):(u.push(d(t.substring(a,i.start))),c+=i.start-a);let s=h+1,p=i.end;for(;s${d(t.substring(i.start,p))}`),c+=p-i.start,h=s,a=p,r&&c>o)break}if(a(a[h]=f(n),n.trim()!=="")).join("|"),"gi");for(let n of e){let h=[],a=[],c={...n,preview:"",matchCount:0},u=n.content.matchAll(o);for(let s of Array.from(u))a.push({start:s.index,end:s.index+s[0].length});let i=n.title.matchAll(o);for(let s of Array.from(i))h.push({start:s.index,end:s.index+s[0].length});h.length>0&&(c.title=l.processMatches(c.title,h,!1)),a.length>0?c.preview=l.processMatches(c.content,a):c.preview=d(c.content.substring(0,140)),c.matchCount=h.length+a.length,c.matchCount>0&&r.push(c)}return r.sort((n,h)=>h.matchCount-n.matchCount)}async doSearch(t){let e=performance.now(),r=await this.searchKeywords(t);this.clear();for(let n of r)this.list.append(l.render(n));let o=performance.now();this.resultTitle.innerText=this.generateResultTitle(r.length,((o-e)/1e3).toPrecision(1))}generateResultTitle(t,e){return this.resultTitleTemplate.replace("#PAGES_COUNT",t).replace("#TIME_SECONDS",e)}async getData(){if(!this.data){let t=this.form.dataset.json;this.data=await fetch(t).then(r=>r.json());let e=new DOMParser;for(let r of this.data)r.content=e.parseFromString(r.content,"text/html").body.innerText}return this.data}bindSearchForm(){let t="",e=r=>{r.preventDefault();let o=this.input.value.trim();if(l.updateQueryString(o,!0),o==="")return t="",this.clear();t!==o&&(t=o,this.doSearch(o.split(" ")))};this.input.addEventListener("input",e),this.input.addEventListener("compositionend",e)}clear(){this.list.innerHTML="",this.resultTitle.innerText=""}bindQueryStringChange(){window.addEventListener("popstate",t=>{this.handleQueryString()})}handleQueryString(){let e=new URL(window.location.toString()).searchParams.get("keyword");this.input.value=e,e?this.doSearch(e.split(" ")):this.clear()}static updateQueryString(t,e=!1){let r=new URL(window.location.toString());t===""?r.searchParams.delete("keyword"):r.searchParams.set("keyword",t),e?window.history.replaceState("","",r.toString()):window.history.pushState("","",r.toString())}static render(t){return createElement("article",null,createElement("a",{href:t.permalink},createElement("div",{class:"article-details"},createElement("h2",{class:"article-title",dangerouslySetInnerHTML:{__html:t.title}}),createElement("section",{class:"article-preview",dangerouslySetInnerHTML:{__html:t.preview}})),t.image&&createElement("div",{class:"article-image"},createElement("img",{src:t.image,loading:"lazy"}))))}};window.addEventListener("load",()=>{setTimeout(function(){let l=document.querySelector(".search-form"),t=l.querySelector("input"),e=document.querySelector(".search-result--list"),r=document.querySelector(".search-result--title");new g({form:l,input:t,list:e,resultTitle:r,resultTitleTemplate:window.searchResultTitleTemplate})},0)});var w=g;})();