<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>~iany/ Posts</title><link>https://blog.iany.me/post/</link><description>Recent content in Posts «~iany/»</description><language>en-US</language><managingEditor>me@iany.me (Ian Yang)</managingEditor><webMaster>me@iany.me (Ian Yang)</webMaster><copyright>CC-BY-SA 4.0</copyright><lastBuildDate>Fri, 20 Feb 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.iany.me/post/index.xml" rel="self" type="application/rss+xml"/><item><title>Power of Monoid, Beauty of Simplicity</title><link>https://blog.iany.me/2026/02/power-of-monoid-beauty-of-simplicity/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2026/02/power-of-monoid-beauty-of-simplicity/</guid><description>&lt;p&gt;A monoid is one of the smallest useful abstractions in algebra: a set closed under an associative binary operation, with an identity element. That simplicity is exactly why it shows up everywhere—from summing numbers and concatenating strings to powering divide-and-conquer algorithms and elegant data structures like finger trees. This post walks through what monoids are, why they give you &amp;ldquo;compute power&amp;rdquo; for free when you can phrase a problem in terms of them, and how to think about choosing the right monoid and predicate when you do.&lt;/p&gt;
&lt;h2 id="what-is-a-monoid"&gt;What is a monoid?&lt;/h2&gt;
&lt;p&gt;A monoid is a set &lt;code&gt;$S$&lt;/code&gt; equipped with a binary operator &lt;code&gt;$\bullet$&lt;/code&gt; and an identity element &lt;code&gt;$e$&lt;/code&gt; (&lt;a href="https://en.wikipedia.org/wiki/Monoid"&gt;Wikipedia&lt;/a&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The operator is closed on &lt;code&gt;$S$&lt;/code&gt;. For all &lt;code&gt;$a, b \in S$&lt;/code&gt;, the result &lt;code&gt;$a \bullet b$&lt;/code&gt; is also in &lt;code&gt;$S$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The operator is associative: For all &lt;code&gt;$a,b,c \in S$&lt;/code&gt;, &lt;code&gt;$(a \bullet b) \bullet c = a \bullet (b \bullet c)$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The identity element &lt;code&gt;$e$&lt;/code&gt; satisfies &lt;code&gt;$e \bullet a = a \bullet e = a$&lt;/code&gt; for all &lt;code&gt;$a \in S$&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, The integer numbers with the operator addition (&lt;code&gt;+&lt;/code&gt;) is a monoid, where the identity element is &lt;code&gt;0&lt;/code&gt;. The integer numbers with the operator multiplication (&lt;code&gt;x&lt;/code&gt;) is also a monoid with the identity element &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The set of finite lists with the operator concatenation is a monoid since:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The operator is closed because concatenation of two finite lists is also a finite list.&lt;/li&gt;
&lt;li&gt;The operator is associative, because both &lt;code&gt;$(a \bullet b) \bullet c$&lt;/code&gt; and &lt;code&gt;$a \bullet (b \bullet c)$&lt;/code&gt; result in a new list by placing elements of &lt;code&gt;$a, b, c$&lt;/code&gt; consecutively.&lt;/li&gt;
&lt;li&gt;The identity element is the empty list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The integer numbers with the operator &lt;code&gt;max&lt;/code&gt; is a counterexample. The operator is closed and associative, but there is no identity element. Given any integer &lt;code&gt;$e$&lt;/code&gt;, there&amp;rsquo;s always a smaller integer &lt;code&gt;$a$&lt;/code&gt; such that &lt;code&gt;$e \bullet a = e \ne a$&lt;/code&gt;. However, &lt;code&gt;max&lt;/code&gt; on the integer set with a lower bound is a monoid, such as the non-negative integers where the identity element is the lower bound &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="divide-and-conquer-why-associativity-matters"&gt;Divide and conquer: why associativity matters&lt;/h2&gt;
&lt;p&gt;At first glance, associativity may seem too trivial to be useful in programming. However, associativity is what enables powerful divide-and-conquer strategies, where problems can be split into parts, solved independently, and then safely recombined.&lt;/p&gt;
&lt;h3 id="exponentiation-by-squaring"&gt;Exponentiation by Squaring&lt;/h3&gt;
&lt;p&gt;Let’s begin with a simple application: repeatedly applying the binary operator to the same element.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\underbrace{a \bullet a \bullet \cdots \bullet a}_{a \text{ appears } n \text{ times}}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of applying the binary operator &lt;code&gt;$n-1$&lt;/code&gt; times sequentially, we exploit associativity to group every two instances of &lt;code&gt;$a$&lt;/code&gt; together recursively. This gives us a smaller problem when n is even:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\underbrace{(a \bullet a) \bullet \cdots \bullet (a \bullet a)}_{(a \bullet a) \text{ appears } \frac{n}{2} \text{ times}}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When n is odd:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
a \bullet \underbrace{(a \bullet a) \bullet \cdots \bullet (a \bullet a)}_{(a \bullet a) \text{ appears } \frac{n-1}{2} \text{ times}}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We only need to compute &lt;code&gt;$a \bullet a$&lt;/code&gt; once to turn the problem of size &lt;code&gt;$n$&lt;/code&gt; to size &lt;code&gt;$n/2$&lt;/code&gt;. Repeating the process on &lt;code&gt;$(a \bullet a)$&lt;/code&gt; gives the &lt;a href="https://en.wikipedia.org/wiki/Exponentiation_by_squaring"&gt;Exponentiation by Squaring&lt;/a&gt; algorithm, which requires at most &lt;code&gt;$\displaystyle 2 \lfloor \log _{2}n\rfloor$&lt;/code&gt; computations that is more efficient than &lt;code&gt;$n-1$&lt;/code&gt; when &lt;code&gt;$n$&lt;/code&gt; is greater than 4.&lt;/p&gt;
&lt;p&gt;For integers or real numbers under multiplication (&lt;code&gt;×&lt;/code&gt;), exponentiation by squaring is an efficient algorithm to compute positive integer powers. Since the set of elliptic curve points under point addition forms a monoid, this same method can also be used to compute &lt;a href="https://kb.iany.me/para/lets/c/Cryptography/Elliptic&amp;#43;Curve&amp;#43;Scalar&amp;#43;Multiplication"&gt;Elliptic Curve Scalar Multiplication&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="general-divide-and-conquer-search-algorithm"&gt;General Divide-and-Conquer Search Algorithm&lt;/h3&gt;
&lt;p&gt;We can generalize the divide-and-conquer method to search an element in a sequence based solely on associativity.&lt;/p&gt;
&lt;p&gt;The result of applying the monoid operator to a sequence from left to right serves as a summary of that sequence. If a predicate can determine whether a given element is in the sequence based solely on this summary, we can devise a general divide-and-conquer search algorithm.&lt;/p&gt;
&lt;p&gt;Let’s say we want to search for a target element in the sequence &lt;code&gt;$t_1, \ldots, t_n$&lt;/code&gt;, where each &lt;code&gt;$t_i$&lt;/code&gt; belongs to a monoid &lt;code&gt;$(S, \bullet, e)$&lt;/code&gt;. Here, &lt;code&gt;$S$&lt;/code&gt; is the underlying set, &lt;code&gt;$\bullet$&lt;/code&gt; is the binary operation, and &lt;code&gt;$e$&lt;/code&gt; is the identity element.&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t know which kind of predicates works for the search algorithm. Let&amp;rsquo;s give a best guess that the predicate &lt;code&gt;$p$&lt;/code&gt; is a function of the monoid &amp;ldquo;summary&amp;rdquo; that &lt;code&gt;$p(t_1 \bullet \cdots \bullet t_n)$&lt;/code&gt; is true if and only if &lt;code&gt;$t$&lt;/code&gt; is in the sequence &lt;code&gt;$t_1, \dots, t_n$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Assume that &lt;code&gt;$p(t_1 \bullet \cdots \bullet t_n)$&lt;/code&gt; is true, thus the target element &lt;code&gt;$t$&lt;/code&gt; is in the sequence. We divide the sequence into two halves: &lt;code&gt;$t_1,\ldots,t_k$&lt;/code&gt; and &lt;code&gt;$t_{k+1},\ldots,t_n$&lt;/code&gt;, where &lt;code&gt;$1 \le k \le n$&lt;/code&gt;. We then evaluate &lt;code&gt;$p(t_1 \bullet \cdots \bullet t_k)$&lt;/code&gt; to determine whether the target element lies in the first or second half, and continue the search.&lt;/p&gt;
&lt;p&gt;Based on this observation, we can deduce the following property of the predicate: there exists an index &lt;code&gt;$x$&lt;/code&gt; such that&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
p(t_1 \bullet \cdots \bullet t_k) := \begin{cases}
\text{false} &amp;amp; \text{if } k &amp;lt; x, \\
\text{true} &amp;amp; \text{if } k \ge x.
\end{cases}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$t_x$&lt;/code&gt; is the target element if such &lt;code&gt;$x$&lt;/code&gt; exists; otherwise, the target element does not exist in the sequence.&lt;/p&gt;
&lt;p&gt;Intuitively, the target element is the turning point at which the predicate on the running summary changes from false to true.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img class="kg-image"
alt="Monoid Search Turning Point.excalidraw"
loading="lazy"
src="https://blog.iany.me/2026/02/power-of-monoid-beauty-of-simplicity/Monoid%20search%20turning%20point.excalidraw.svg" /&gt;
&lt;/figure&gt;
&lt;p&gt;Note that &lt;code&gt;$p$&lt;/code&gt; makes sense only on the summary of any prefix of the sequence. If we need to continue the search in the second half, we must remember the summary of the scanned prefix.&lt;/p&gt;
&lt;p&gt;Now we can define the search algorithm &lt;code&gt;$\mathrm{Search}(p, s, \{t_i,\ldots,t_j\})$&lt;/code&gt; where&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$s$&lt;/code&gt; is the summary of scanned prefix &lt;code&gt;$t_1 \bullet \cdots \bullet t_{i-1}$&lt;/code&gt; when &lt;code&gt;$i &amp;gt; 1$&lt;/code&gt; or the identity element &lt;code&gt;$e$&lt;/code&gt; otherwise.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$t_i, \ldots, t_j$&lt;/code&gt; is the sub-range to search next.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$p$&lt;/code&gt; is the predicate as defined above&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The algorithm proceeds as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;$p(s \bullet t_i \bullet \cdots \bullet t_j$&lt;/code&gt;) is false, the target element does not exist. The algorithm aborts with an error.&lt;/li&gt;
&lt;li&gt;Otherwise, if there&amp;rsquo;s only one element (&lt;code&gt;$i = j$&lt;/code&gt;), &lt;code&gt;$t_i$&lt;/code&gt; is the target element. The algorithm aborts with the found result.&lt;/li&gt;
&lt;li&gt;Otherwise, choose a pivot index &lt;code&gt;$i \le m \lt j$&lt;/code&gt; to split the sequence into two nonempty halves: &lt;code&gt;$t_i, \ldots, t_m$&lt;/code&gt; and &lt;code&gt;$t_{m+1},\ldots,t_j$&lt;/code&gt;. Test &lt;code&gt;$p(s \bullet t_i \bullet \cdots \bullet t_m)$&lt;/code&gt; that
&lt;ul&gt;
&lt;li&gt;If it is true, continue the search in the first half: &lt;code&gt;$\mathrm{Search}(p, s, \{t_i,\ldots,t_m\})$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Otherwise, continue the search in the second half: &lt;code&gt;$\mathrm{Search}(p, s \bullet (t_i \bullet \cdots \bullet t_m),\{t_{m+1},\ldots,t_j\})$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The algorithm starts with &lt;code&gt;$\mathrm{Search}(p, e, \{t_1, \ldots, t_k\})$&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="application-random-access-sequence"&gt;Application: Random-Access Sequence&lt;/h3&gt;
&lt;p&gt;An application of the search algorithm is accessing the nth element in the sequence.&lt;/p&gt;
&lt;p&gt;We initialize the sequence to all 1s and use the monoid of non-negative integers with addition &lt;code&gt;$(\mathbb{N},+,0)$&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\underbrace{1, \ldots, 1}_{n \text{ times}}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The predicate to find the i-th (starting from 0) element is:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
p_i(s) := s &amp;gt; i
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It may seem silly to search for the i-th 1 in a sequence of 1s, but we can store any data in the sequence and attach the monoid values as annotations to guide the search algorithm.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img class="kg-image"
alt="Sequence Monoid Annotations.excalidraw"
loading="lazy"
src="https://blog.iany.me/2026/02/power-of-monoid-beauty-of-simplicity/Sequence%20Monoid%20Annotations.excalidraw.svg" /&gt;
&lt;/figure&gt;
&lt;h3 id="application-max-priority-queue"&gt;Application: Max-Priority Queue&lt;/h3&gt;
&lt;p&gt;Another application is finding the element with the max priority.&lt;/p&gt;
&lt;p&gt;We use the monoid of non-negative integers with operator &lt;code&gt;max&lt;/code&gt; &lt;code&gt;$(\mathbb{N},\mathrm{max},0)$&lt;/code&gt; and assume that the maximum value has the maximum priority.&lt;/p&gt;
&lt;p&gt;The predicate to find the element with the max priority is&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
p(s) := s = m
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;$m$&lt;/code&gt; is the monoid summary of the entire sequence—that is, the maximum value in the sequence. The predicate checks whether the summary equals to &lt;code&gt;$m$&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="annotated-search-tree"&gt;Annotated Search Tree&lt;/h3&gt;
&lt;p&gt;A natural way to support the divide-and-conquer search is an &lt;em&gt;annotated binary tree&lt;/em&gt;. Store the sequence elements at the leaves, and at each node store the monoid summary of the subtree—e.g. the sum of lengths or the maximum priority in that subtree. The predicate can then be evaluated on the left subtree’s annotation to decide whether to descend left or right, and the prefix summary is updated when going right by combining it with the left subtree’s summary.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img class="kg-image"
alt="Annotated Binary Tree.excalidraw"
loading="lazy"
src="https://blog.iany.me/2026/02/power-of-monoid-beauty-of-simplicity/Annotated%20binary%20tree.excalidraw.svg" /&gt;
&lt;/figure&gt;
&lt;p&gt;A plain binary tree can degenerate to a list in the worst case, so operations may become linear. A more advanced structure, the &lt;em&gt;finger tree&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;1&lt;/span&gt;&lt;/sup&gt;, keeps the tree balanced and supports efficient access at both ends and in the middle; each node carries a monoidal “measure” of its subtree, and the same search strategy applies. In Haskell, &lt;a href="https://hackage-content.haskell.org/package/containers-0.8/docs/Data-Sequence.html"&gt;Data.Sequence&lt;/a&gt; from the &lt;code&gt;containers&lt;/code&gt; library implements sequences as finger trees with size (length) as the measure, giving &lt;code&gt;$O(\log n)$&lt;/code&gt; indexing, splitting, and concatenation.&lt;/p&gt;
&lt;h3 id="utility-of-the-identity-element"&gt;Utility of the Identity Element&lt;/h3&gt;
&lt;p&gt;The general divide-and-conquer algorithm does not require a monoid—only a semigroup. A semigroup is a fancy word for a set equipped with a closed, associative binary operator but lacking an identity element. The presence of an identity element makes monoids convenient to work with.&lt;/p&gt;
&lt;p&gt;The identity element serves as a natural default value or starting point for algorithms. For instance, in the search algorithm, the summary of the scanned prefix is initialized to &lt;code&gt;$e$&lt;/code&gt;. Without an identity element, we need an additional flag to indicate whether any prefix has been scanned, and the algorithm would have to branch conditionally based on that flag.&lt;/p&gt;
&lt;h2 id="the-art-of-choosing-monoid-and-predicate"&gt;The art of choosing monoid and predicate&lt;/h2&gt;
&lt;p&gt;In the random-access example we used &lt;code&gt;$(\mathbb{N}, +, 0)$&lt;/code&gt; and annotated each position with &lt;code&gt;$1$&lt;/code&gt;—the summary of a segment is its length, and the predicate &lt;code&gt;$s &amp;gt; i$&lt;/code&gt; tells us whether the &lt;code&gt;$i$&lt;/code&gt;-th element lies in the prefix we have so far. In the max-priority queue we used &lt;code&gt;$(\mathbb{N}, \max, 0)$&lt;/code&gt; (or a bounded variant): the summary is the maximum value in the segment, and the predicate &lt;code&gt;$s = m$&lt;/code&gt; identifies the segment that contains the global maximum. In both cases, the monoid was chosen so that the &lt;em&gt;combined&lt;/em&gt; summary over a range is exactly what the predicate needs to decide where to go next.&lt;/p&gt;
&lt;p&gt;The flip side is that finding both the right monoid and the right predicate can be tricky. At each step the search has access only to the monoid summary of the prefix (or segment) seen so far, so the predicate must be decided from that summary alone. The monoid must be rich enough to supply the information the predicate needs. Sometimes the natural summary (e.g. sum or max) suggests the predicate (e.g. &lt;code&gt;$s &amp;gt; i$&lt;/code&gt; or &lt;code&gt;$s = m$&lt;/code&gt;). Sometimes you must try a different carrier or operation, or encode extra information into the monoid (e.g. pairs or custom types), so that the predicate can be expressed. There is no universal recipe—it is a matter of design and experimentation. Reframe the problem as: “What do I need to know about a segment to decide the next step?” Then choose a monoid that can represent that knowledge and a predicate that uses it.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Hinze, R., &amp;amp; Paterson, R. (2006). Finger trees: A simple general-purpose data structure. &lt;em&gt;Journal of Functional Programming, 16&lt;/em&gt;(2), 197–217. Cambridge University Press. &lt;a href="https://www.cs.ox.ac.uk/ralf.hinze/publications/FingerTrees.pdf"&gt;https://www.cs.ox.ac.uk/ralf.hinze/publications/FingerTrees.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/algorithm/">Algorithm</category><category domain="https://blog.iany.me/tags/math/">Math</category><category domain="https://blog.iany.me/tags/programming/">Programming</category></item><item><title>Use Bun for Shell Scripts</title><link>https://blog.iany.me/2026/02/use-bun-for-shell-scripts/</link><pubDate>Sat, 07 Feb 2026 00:00:00 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2026/02/use-bun-for-shell-scripts/</guid><description>&lt;p&gt;I&amp;rsquo;ve moved most of my small scripts to Bun. It sidesteps Windows&amp;rsquo; lack of shebang support and the Git-for-Windows/WSL bash dance, and gives you one runtime for both ad-hoc shell-style commands and full scripts.&lt;/p&gt;
&lt;h2 id="problems-bun-solves"&gt;Problems Bun solves&lt;/h2&gt;
&lt;p&gt;Windows doesn&amp;rsquo;t execute &lt;code&gt;#!/usr/bin/env python3&lt;/code&gt; or &lt;code&gt;#!/bin/bash&lt;/code&gt;, so scripts written for Linux or macOS usually need a separate PowerShell version. Bun compiles JavaScript to native executables, so a single script can run cross-platform without shebangs.&lt;/p&gt;
&lt;p&gt;Mise &lt;a href="https://mise.jdx.dev/tasks/file-tasks.html"&gt;file tasks&lt;/a&gt; have started to support shebangs on Windows in the latest release, but they rely on system &lt;code&gt;bash.exe&lt;/code&gt;, which often points at WSL. Bun does not. You run &lt;code&gt;bun ./script.js&lt;/code&gt; and embed shell commands via &lt;code&gt;Bun.$&lt;/code&gt;; the script uses Bun&amp;rsquo;s bundled bash environment.&lt;/p&gt;
&lt;h2 id="why-bun-for-shell-scripts"&gt;Why Bun for shell scripts&lt;/h2&gt;
&lt;p&gt;The command &lt;code&gt;bun build ./script.ts --compile --outfile script&lt;/code&gt; &lt;a href="https://bun.com/docs/bundler/executables"&gt;produces a single binary&lt;/a&gt;, so no shebang or interpreter is needed on Windows. The &lt;code&gt;Bun.build&lt;/code&gt; API is available too; I use a &lt;code&gt;build.js&lt;/code&gt; script to walk a directory and compile all executables. Pre-built binaries start quickly—Bun&amp;rsquo;s docs cite ~5ms vs Node&amp;rsquo;s ~25ms for a simple script, which helps for small, frequently run scripts.&lt;/p&gt;
&lt;p&gt;Bun has strong support for shell-style scripting. &lt;a href="https://bun.com/docs/runtime/shell"&gt;Bun Shell&lt;/a&gt; runs bash commands; the main entry point is &lt;code&gt;Bun.$&lt;/code&gt;, which constructs shell commands from template literals for safe interpolation, similar to &lt;a href="https://github.com/google/zx"&gt;google/zx&lt;/a&gt;. On Windows, Bun ships with MSYS, so you get a consistent bash environment and common Linux CLI tools without extra setup.&lt;/p&gt;
&lt;p&gt;Other useful features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Bun.secrets&lt;/code&gt;—Securely store and retrieve secrets using the OS keystore (Windows Credential Manager, macOS Keychain, Linux libsecret). &lt;a href="https://bun.com/docs/api/secrets"&gt;Secrets - Bun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Built-in support for glob patterns, YAML parsing, ANSI colors, and more. &lt;a href="https://bun.com/docs/runtime"&gt;Bun Runtime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="short-example"&gt;Short example&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;import { $ } from &amp;quot;bun&amp;quot;;
const out = await $`echo hello`.text();
console.log(out); // hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it with &lt;code&gt;bun run script.js&lt;/code&gt; or &lt;code&gt;bun ./script.js&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="obsidian-notes"&gt;Obsidian Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kb.iany.me/para/lets/d/Development&amp;#43;Environment/JavaScript&amp;#43;Shell&amp;#43;Scripting"&gt;JavaScript Shell Scripting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/bun/">Bun</category><category domain="https://blog.iany.me/tags/javascript/">JavaScript</category><category domain="https://blog.iany.me/tags/script/">Script</category><category domain="https://blog.iany.me/tags/windows/">Windows</category><category domain="https://blog.iany.me/tags/dev-environment/">Dev Environment</category></item><item><title>Use tmux for PowerShell in Windows Terminal</title><link>https://blog.iany.me/2026/01/use-tmux-for-powershell-in-windows-terminal/</link><pubDate>Thu, 29 Jan 2026 23:30:42 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2026/01/use-tmux-for-powershell-in-windows-terminal/</guid><description>&lt;p&gt;You can get tmux session persistence and multiplexing in Windows Terminal by running tmux inside WSL and setting the default shell to PowerShell. New panes and windows will then start &lt;code&gt;pwsh.exe&lt;/code&gt; instead of a Linux shell. Here’s a minimal setup using small wrappers and the &lt;code&gt;tmux -C attach&lt;/code&gt; trick to configure new sessions.&lt;/p&gt;
&lt;h2 id="why-tmux-under-wsl-with-powershell"&gt;Why tmux under WSL with PowerShell?&lt;/h2&gt;
&lt;p&gt;Since tmux does not run natively on Windows, the standard approach is to use it through WSL. However, if you launch &lt;code&gt;wsl tmux&lt;/code&gt; from Windows Terminal, all panes will default to your WSL shell. When working in a Windows directory, I prefer to use native PowerShell within the Windows environment.&lt;/p&gt;
&lt;p&gt;To ensure every tmux pane runs PowerShell, configure tmux to use &lt;code&gt;pwsh.exe&lt;/code&gt; as its default command. When you launch &lt;code&gt;pwsh.exe&lt;/code&gt; from WSL within a Windows directory, it brings us back to the Windows environment.&lt;/p&gt;
&lt;h2 id="wrappers-so-you-can-call-tmux-from-powershell"&gt;Wrappers so you can call tmux from PowerShell&lt;/h2&gt;
&lt;p&gt;Put these in a directory on your &lt;code&gt;PATH&lt;/code&gt; (e.g. &lt;code&gt;~/Documents/PowerShell/bin&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PowerShell&lt;/strong&gt; — &lt;code&gt;tmux.ps1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;wsl tmux $args
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Cmd&lt;/strong&gt; — &lt;code&gt;tmux.cmd&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-batch"&gt;@echo off
wsl tmux %*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From PowerShell or Cmd you can then run &lt;code&gt;tmux new&lt;/code&gt;, &lt;code&gt;tmux attach&lt;/code&gt;, etc., and they all go to WSL’s tmux.&lt;/p&gt;
&lt;h2 id="creating-powershell-sessions"&gt;Creating PowerShell Sessions&lt;/h2&gt;
&lt;p&gt;The next piece is a script that creates a new session whose default command is &lt;code&gt;pwsh.exe&lt;/code&gt;, or attaches if that session already exists. Example usage: &lt;code&gt;tmux-pwsh dev&lt;/code&gt; or &lt;code&gt;tmux-pwsh work&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The script sets the default command only for sessions it creates; it does not affect sessions started with a standard &lt;code&gt;tmux new-session&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;tmux-pwsh.ps1&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;param(
[Parameter(Mandatory=$true)]
[string]$SessionName
)
$pwshCommand = &amp;quot;exec pwsh.exe -nologo&amp;quot;
# Check if session exists using exact name match
wsl tmux has-session -t &amp;quot;\=$SessionName&amp;quot; 2&amp;gt;$null
if ($LASTEXITCODE -ne 0) {
wsl tmux new-session -d -s $SessionName $pwshCommand
echo &amp;quot;set-option default-command `&amp;quot;$pwshCommand`&amp;quot;&amp;quot; | `
wsl tmux -C attach -t &amp;quot;\=$SessionName&amp;quot; &amp;gt;$null
}
wsl tmux attach -t &amp;quot;\=$SessionName&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;=&lt;/code&gt; prefix does an exact session name match and must be escaped when sending the command from PowerShell to WSL.&lt;/li&gt;
&lt;li&gt;The new session specifies command &lt;code&gt;pwsh.exe&lt;/code&gt; via the command argument.&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;wsl tmux set-option&lt;/code&gt; immediately after creating the session does not work because there&amp;rsquo;s a brief window where the session is not available for setting options. Instead, sending &lt;code&gt;set-option&lt;/code&gt; via &lt;code&gt;tmux -C attach&lt;/code&gt; reliably applies the setting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After this, splitting panes and creating new windows in that session will all start &lt;code&gt;pwsh.exe -nologo&lt;/code&gt; in Windows Terminal.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use WSL for tmux and PowerShell as the default command by setting tmux’s default command to &lt;code&gt;exec pwsh.exe -nologo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tmux.ps1&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;tmux.cmd&lt;/code&gt;&lt;/strong&gt; — thin wrappers so &lt;code&gt;tmux&lt;/code&gt; from Windows means &lt;code&gt;wsl tmux&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tmux-pwsh.ps1 &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/strong&gt; — create (or attach to) a named session that uses PowerShell as the default command.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once these are on your &lt;code&gt;PATH&lt;/code&gt;, run e.g. &lt;code&gt;tmux-pwsh dev&lt;/code&gt; from Windows Terminal to get a tmux session where every pane is PowerShell.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/tmux/">Tmux</category><category domain="https://blog.iany.me/tags/powershell/">PowerShell</category><category domain="https://blog.iany.me/tags/wsl/">WSL</category><category domain="https://blog.iany.me/tags/windows-terminal/">Windows Terminal</category></item><item><title>Backup Ignored Files with Git Remote Branch</title><link>https://blog.iany.me/2025/12/backup-ignored-files-with-git-remote-branch/</link><pubDate>Fri, 12 Dec 2025 03:22:58 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2025/12/backup-ignored-files-with-git-remote-branch/</guid><description>&lt;p&gt;When working with Git repositories, there are often files that need to be backed up but shouldn&amp;rsquo;t be committed to the main branch. These might include local development settings, IDE configuration files, personal notes, or development scripts that are specific to your workflow. The challenge is finding a way to back up these ignored files without polluting the main repository history.&lt;/p&gt;
&lt;h2 id="the-original-solution"&gt;The Original Solution&lt;/h2&gt;
&lt;p&gt;My original approach was to use a separate repository to store backup files for all repositories and create symbolic links. This worked, but had several drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backup files were disconnected from their source repositories, making it harder to track what belongs where&lt;/li&gt;
&lt;li&gt;Backing up new files required moving them to the backup repository and then creating symbolic links&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-better-approach-remote-branch"&gt;A Better Approach: Remote Branch&lt;/h2&gt;
&lt;p&gt;Instead of using a separate repository, we can use a remote branch within the same repository. This approach offers several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everything stays in one repository&lt;/li&gt;
&lt;li&gt;When you clone the repository, the backup branch comes with it&lt;/li&gt;
&lt;li&gt;Backup files are stored in a separate branch, keeping the main branch clean&lt;/li&gt;
&lt;li&gt;Full Git history for backup files, just like any other branch&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-script"&gt;The Script&lt;/h2&gt;
&lt;p&gt;With AI assistance, I developed &lt;code&gt;git-store-file&lt;/code&gt;, a tool for managing ignored files by storing them in a remote branch (default: &lt;code&gt;origin/_store&lt;/code&gt;). This keeps backup files separate from your working branch without interfering with regular development. The script is available in &lt;a href="https://github.com/doitian/dotfiles-public/blob/master/default/bin/git-store-file"&gt;Python&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="installation"&gt;Installation&lt;/h3&gt;
&lt;p&gt;Download the script and make it executable:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;curl -o ~/bin/git-store-file https://raw.githubusercontent.com/doitian/dotfiles-public/master/default/bin/git-store-file
chmod +x ~/bin/git-store-file
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="usage"&gt;Usage&lt;/h3&gt;
&lt;p&gt;The script has four main commands: &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;restore&lt;/code&gt;, and &lt;code&gt;ls&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="store-files"&gt;Store Files&lt;/h4&gt;
&lt;p&gt;Store one or more files to the remote branch, including files that are ignored by Git:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;# Store a single file
git-store-file config.local.json
# Store multiple files
git-store-file config.local.json secrets.env
# Use a custom branch name
git-store-file --branch backup config.local.json
# Use a custom remote
git-store-file --remote upstream config.local.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you run the store command, the script will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a temporary Git index to avoid conflicts with your working directory&lt;/li&gt;
&lt;li&gt;Load the target branch&amp;rsquo;s current state into the temporary index&lt;/li&gt;
&lt;li&gt;Add the specified files using &lt;code&gt;git add -f&lt;/code&gt; to include ignored files&lt;/li&gt;
&lt;li&gt;Create a commit with the changes&lt;/li&gt;
&lt;li&gt;Display commit statistics and prompt for confirmation&lt;/li&gt;
&lt;li&gt;Push the commit to the remote branch&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="check-status"&gt;Check Status&lt;/h4&gt;
&lt;p&gt;Check which files stored in the remote branch have been modified in your local working directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;# Check status
git-store-file status
# Show diff for modified files
git-store-file status --diff
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="restore-files"&gt;Restore Files&lt;/h4&gt;
&lt;p&gt;Restore files from the remote branch to your working directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;# Restore a specific file
git-store-file restore config.local.json
# Restore all files from the branch
git-store-file restore
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="list-files"&gt;List Files&lt;/h4&gt;
&lt;p&gt;List all files stored in the remote branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;git-store-file ls
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="options"&gt;Options&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-b, --branch BRANCH&lt;/code&gt;: Specify the branch name (default: &lt;code&gt;_store&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-r, --remote REMOTE&lt;/code&gt;: Specify the remote name (default: &lt;code&gt;origin&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h, --help&lt;/code&gt;: Show help message&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="examples"&gt;Examples&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;# Store local configuration
git-store-file .env.local
# Check what's changed
git-store-file status -d
# Restore after cloning
git-store-file restore .env.local
# List all backed up files
git-store-file ls
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="how-it-works"&gt;How It Works&lt;/h2&gt;
&lt;p&gt;The script uses Git&amp;rsquo;s low-level plumbing commands to manipulate the repository without affecting your working directory:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Temporary Index&lt;/strong&gt;: Uses the &lt;code&gt;GIT_INDEX_FILE&lt;/code&gt; environment variable to create a temporary index, completely isolated from your working directory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read Tree&lt;/strong&gt;: Loads the target branch&amp;rsquo;s tree structure into the temporary index&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Force Add&lt;/strong&gt;: Uses &lt;code&gt;git add -f&lt;/code&gt; to add files even if they&amp;rsquo;re listed in &lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit Tree&lt;/strong&gt;: Creates a commit object directly using &lt;code&gt;git commit-tree&lt;/code&gt;, bypassing the normal commit workflow&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct Push&lt;/strong&gt;: Pushes the commit hash directly to the remote branch reference&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This low-level approach ensures that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your working directory remains unchanged&lt;/li&gt;
&lt;li&gt;The main branch is never affected&lt;/li&gt;
&lt;li&gt;Ignored files can be stored without modifying &lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The backup branch maintains full Git history&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;git-store-file&lt;/code&gt; script solves the problem of backing up ignored files in a clean, Git-native way. By leveraging a remote branch, it keeps everything in one repository while maintaining clear separation between your main codebase and backup files. Whether you&amp;rsquo;re managing local configurations, IDE settings, or personal development scripts, this tool provides a simple and reliable backup solution that integrates seamlessly with your existing Git workflow.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/git/">Git</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/backup/">Backup</category></item><item><title>How to Activate mise for Cursor When It Runs Shell Commands</title><link>https://blog.iany.me/2025/11/how-to-activate-mise-for-cursor-when-it-runs-shell-commands/</link><pubDate>Wed, 26 Nov 2025 11:31:37 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2025/11/how-to-activate-mise-for-cursor-when-it-runs-shell-commands/</guid><description>&lt;p&gt;When using &lt;a href="https://mise.jdx.dev/"&gt;mise&lt;/a&gt; with Cursor, you may notice that the mise environment is not activated when Cursor executes shell commands. This occurs because Cursor launches a non-login shell, which initializes differently than an interactive login shell and therefore does not automatically source your usual mise setup.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Cursor runs commands in a non-login shell, which means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;zsh&lt;/strong&gt;: Only &lt;code&gt;.zshenv&lt;/code&gt; is loaded (not &lt;code&gt;.zshrc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;bash&lt;/strong&gt;: Only &lt;code&gt;.bash_profile&lt;/code&gt; is loaded (not &lt;code&gt;.bashrc&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;ve configured mise activation in &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.zprofile&lt;/code&gt;, it won&amp;rsquo;t be available when Cursor executes commands.&lt;/p&gt;
&lt;h2 id="the-solution"&gt;The Solution&lt;/h2&gt;
&lt;p&gt;The solution is to activate mise in &lt;code&gt;.zshenv&lt;/code&gt; (for zsh) or &lt;code&gt;.bash_profile&lt;/code&gt; (for bash) when the file is loaded by Cursor. You can detect Cursor by checking for the &lt;code&gt;CURSOR_AGENT&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;p&gt;Add this to your &lt;code&gt;~/.zshenv&lt;/code&gt; or &lt;code&gt;~/.bash_profile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;# Activate mise when running in Cursor
if [[ -n &amp;quot;$CURSOR_AGENT&amp;quot; ]]; then
eval &amp;quot;$(mise activate)&amp;quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="why-this-works"&gt;Why This Works&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.zshenv&lt;/code&gt; (or &lt;code&gt;.bash_profile&lt;/code&gt;) is always sourced for non-login shells&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;CURSOR_AGENT&lt;/code&gt; environment variable is set when Cursor runs commands&lt;/li&gt;
&lt;li&gt;By conditionally activating mise only when &lt;code&gt;CURSOR_AGENT&lt;/code&gt; is present, you avoid potential conflicts or slowdowns in other non-login shell contexts&lt;/li&gt;
&lt;li&gt;This ensures mise and all its configured tools are available when Cursor executes terminal commands&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After adding this configuration, rerun Cursor chats, and mise should be available in all Cursor shell commands.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/mise/">Mise</category><category domain="https://blog.iany.me/tags/cursor/">Cursor</category><category domain="https://blog.iany.me/tags/shell/">Shell</category><category domain="https://blog.iany.me/tags/zsh/">Zsh</category><category domain="https://blog.iany.me/tags/vibe-coding/">Vibe Coding</category></item><item><title>Temporary Vi Mode in PowerShell</title><link>https://blog.iany.me/2025/11/temporary-vi-mode-in-powershell/</link><pubDate>Sat, 22 Nov 2025 23:17:05 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2025/11/temporary-vi-mode-in-powershell/</guid><description>&lt;p&gt;I miss the feature in readline (Bash/Zsh) where &lt;code&gt;Ctrl+x, Ctrl+v&lt;/code&gt; switches to Vi command mode temporarily. In that workflow, entering Insert mode switches back to Emacs mode. &lt;code&gt;PSReadLine&lt;/code&gt; has a command &lt;code&gt;ViCommandMode&lt;/code&gt;, but binding it directly in Emacs mode will report errors on every key input.&lt;/p&gt;
&lt;p&gt;The solution requires handling the mode change event to toggle the global &lt;code&gt;EditMode&lt;/code&gt; between &lt;code&gt;Emacs&lt;/code&gt; and &lt;code&gt;Vi&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When entering &amp;ldquo;Insert&amp;rdquo; mode (the default for Emacs users), we force the mode to &lt;code&gt;Emacs&lt;/code&gt; and set up the trigger for the temporary Vi mode. When that trigger (&lt;code&gt;Ctrl+x, Ctrl+v&lt;/code&gt;) is fired, we switch the edit mode to &lt;code&gt;Vi&lt;/code&gt; and jump to command mode.&lt;/p&gt;
&lt;p&gt;Here is the configuration for your &lt;code&gt;$PROFILE&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;function OnViModeChange {
if ($args[0] -eq 'Insert') {
Set-PSReadLineOption -EditMode Emacs
Set-PSReadlineKeyHandler -Chord &amp;quot;Ctrl+x,Ctrl+v&amp;quot; -ScriptBlock {
Set-PSReadLineOption -EditMode Vi
[Microsoft.PowerShell.PSConsoleReadLine]::ViCommandMode()
}
# Add other Emacs key bindings here.
# Switching `EditMode` resets key bindings to their defaults.
# Remember to reconfigure them.
# Set-PSReadlineKeyHandler -Chord &amp;quot;Ctrl+w&amp;quot; -Function BackwardKillWord
}
}
Set-PSReadLineOption -ViModeIndicator Script
Set-PSReadLineOption -ViModeChangeHandler $Function:OnViModeChange
Set-PSReadLineOption -EditMode Emacs
# Reuse the function to start Emacs mode
OnViModeChange Insert
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Switching &lt;code&gt;EditMode&lt;/code&gt; resets key bindings to their defaults. Remember to reconfigure them.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/powershell/">PowerShell</category><category domain="https://blog.iany.me/tags/vim/">Vim</category><category domain="https://blog.iany.me/tags/emacs/">Emacs</category><category domain="https://blog.iany.me/tags/console/">Console</category></item><item><title>Study on Quotient Spaces</title><link>https://blog.iany.me/2025/11/study-on-quotient-spaces/</link><pubDate>Tue, 18 Nov 2025 21:23:25 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2025/11/study-on-quotient-spaces/</guid><description>&lt;p&gt;I&amp;rsquo;m reading &lt;em&gt;Linear Algebra Done Right&lt;/em&gt; by Axler and found the section on quotient spaces difficult to understand, so I researched and took these notes.&lt;/p&gt;
&lt;h2 id="definitions"&gt;Definitions&lt;/h2&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.95 notion: $v + U$
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;p&gt;Suppose &lt;code&gt;$v \in V$&lt;/code&gt; and &lt;code&gt;$U \subseteq V$&lt;/code&gt;. Then &lt;code&gt;$v + U$&lt;/code&gt; is the subset of &lt;code&gt;$V$&lt;/code&gt; defined by&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[v + U = \{v + u : u \in U\}.\]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Also called a translate. &lt;strong&gt;Attention&lt;/strong&gt; that a translate is a set.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.97 definition: &lt;em&gt;translate&lt;/em&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
Suppose &lt;code&gt;$v \in V$&lt;/code&gt; and &lt;code&gt;$U \subseteq V$&lt;/code&gt;, the set &lt;code&gt;$v + U$&lt;/code&gt; is said to be a &lt;em&gt;translate&lt;/em&gt; of &lt;code&gt;$U$&lt;/code&gt;.
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Quotient space is a set of all translates (set of sets):&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.99 definition: &lt;em&gt;quotient space&lt;/em&gt;, $V/U$
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;p&gt;Suppose &lt;code&gt;$U$&lt;/code&gt; is a subspace of &lt;code&gt;$V$&lt;/code&gt;. Then the &lt;em&gt;quotient space&lt;/em&gt; &lt;code&gt;$V/U$&lt;/code&gt; is the set of all translates of &lt;code&gt;$U$&lt;/code&gt;. Thus&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[V/U = \{v + U : v \in V\}.\]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Quotient space is a set of sets. There are duplicates for each &lt;code&gt;$v \in V$&lt;/code&gt; because for some &lt;code&gt;$v_1, v_2 \in V$&lt;/code&gt;, &lt;code&gt;$v_1 + U$&lt;/code&gt; and &lt;code&gt;$v_2 + U$&lt;/code&gt; can be identical set.&lt;/p&gt;
&lt;p&gt;A quotient space &lt;code&gt;$V/U$&lt;/code&gt; is formed by &amp;ldquo;collapsing&amp;rdquo; a subspace &lt;code&gt;$U$&lt;/code&gt; to zero within a larger vector space &lt;code&gt;$V$&lt;/code&gt;. This construction is based on an equivalence relation where two vectors &lt;code&gt;$x, y \in V$&lt;/code&gt; are considered equivalent if their difference lies in &lt;code&gt;$U$&lt;/code&gt;—that is, &lt;code&gt;$x \sim y$&lt;/code&gt; if and only if &lt;code&gt;$x - y \in U$&lt;/code&gt;. &lt;a href="https://en.wikipedia.org/wiki/Quotient_space_%28linear_algebra%29"&gt;wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="lemmas"&gt;Lemmas&lt;/h2&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.101 &lt;em&gt;two translates of a subspace are equal or disjoint&lt;/em&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;p&gt;Suppose &lt;code&gt;$U$&lt;/code&gt; is a subspace of &lt;code&gt;$V$&lt;/code&gt; and &lt;code&gt;$v, w \in V$&lt;/code&gt;. Then&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
v - w \in U \iff v + U = w + U \iff (v + U) \cap (w + U) \neq \emptyset
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;If two translates are not disjoint (the union set is not empty), they must be equal. So they are equal or disjoint.&lt;/p&gt;
&lt;p&gt;All distinct translates of a subspace are disjoint. Given any &lt;code&gt;$v \in V$&lt;/code&gt;, it belongs to only one translate.&lt;/p&gt;
&lt;p&gt;Since the quotient space &lt;code&gt;$V/U$&lt;/code&gt; is a set of translates of a subspace, it is like a disjoint partition of values in &lt;code&gt;$V$&lt;/code&gt;. By using the definition of quotient map&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.104 definition: &lt;em&gt;quotient map&lt;/em&gt;, $\pi$
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;p&gt;Suppose &lt;code&gt;$U$&lt;/code&gt; is a subspace of &lt;code&gt;$V$&lt;/code&gt;. The &lt;em&gt;quotient map&lt;/em&gt; &lt;code&gt;$\pi : V \to V/U$&lt;/code&gt; is the linear map defined by&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[\pi(v) = v + U\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;for each &lt;code&gt;$v \in V$&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;We can write that&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\pi(v_1) = \pi(v_2) \iff v_1 - v_2 \in U
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The quotient map has two essential properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;null space&lt;/strong&gt; of &lt;code&gt;$\pi$&lt;/code&gt; is exactly the subspace &lt;code&gt;$U$&lt;/code&gt;, because &lt;code&gt;$v+U=0+U \iff v-0 \in U \iff v \in U$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;range&lt;/strong&gt; of &lt;code&gt;$\pi$&lt;/code&gt; is the entire quotient space &lt;code&gt;$V/U$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="quotient-space-is-a-vector-space"&gt;Quotient Space Is a Vector Space&lt;/h2&gt;
&lt;p&gt;First define the addition and scalar multiplication operations:&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.102 definition: &lt;em&gt;addition and scalar multiplication on&lt;/em&gt; $V/U$
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;p&gt;Suppose &lt;code&gt;$U$&lt;/code&gt; is a subspace of &lt;code&gt;$V$&lt;/code&gt;. Then addition and scalar multiplication are defined on &lt;code&gt;$V/U$&lt;/code&gt; by&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[\begin{align*}
(v + U) + (w + U) &amp;amp;= (v + w) + U \\
\lambda(v + U) &amp;amp;= (\lambda v) + U
\end{align*}\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;for all &lt;code&gt;$v, w \in V$&lt;/code&gt; and &lt;code&gt;$\lambda \in \mathbf{F}$&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;&lt;code&gt;$v+U$&lt;/code&gt; is not the unique way to represent a member in &lt;code&gt;$V/U$&lt;/code&gt;, because there may exist &lt;code&gt;$v'\ne v$&lt;/code&gt; that &lt;code&gt;$u + U = v' + U$&lt;/code&gt;. The operations make sense only when the choice of &lt;code&gt;$v$&lt;/code&gt; to represent a translate makes no differences.&lt;/p&gt;
&lt;p&gt;Specifically, suppose &lt;code&gt;$v_1, v_2, w_1, w_2 \in V$&lt;/code&gt; such that&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
v_1 + U = v_2 + U \quad\textrm{and}\quad w_1 + U = w_2 + U
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the addition definition:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\begin{align*}
(v_1+U) + (w_1+U) &amp;amp;= (v_1 + w_1) + U \\
(v_2+U) + (w_2+U) &amp;amp;= (v_2 + w_2) + U
\end{align*}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The left side of the two equations indeed are the different representation of the same equation, so we must show that the right side equal: &lt;code&gt;$(v_1 + w_1)+U=(v2+w2)+U$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This applies to scalar multiplication as well:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\begin{align*}
\lambda(v_1 + U) &amp;amp;= (\lambda v_1) + U \\
\lambda(v_2 + U) &amp;amp;= (\lambda v_2) + U
\end{align*}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We must show that &lt;code&gt;$(\lambda v_1) + U = (\lambda v_2) + U$&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="dimension"&gt;Dimension&lt;/h2&gt;
&lt;p&gt;The dimension of the quotient space is given by a simple subtraction, relating the dimension of &lt;code&gt;$V/U$&lt;/code&gt; to the &amp;ldquo;lost&amp;rdquo; dimension of &lt;code&gt;$U$&lt;/code&gt;:&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.105 &lt;em&gt;dimension of quotient space&lt;/em&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;p&gt;Suppose &lt;code&gt;$V$&lt;/code&gt; is finite-dimensional and &lt;code&gt;$U$&lt;/code&gt; is a subspace of V. Then&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[\text{dim } V/U = \text{dim }V - \text{dim }U.\]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id="linear-map-from-vnull-t-to-w"&gt;Linear Map from V/(null T) to W&lt;/h2&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.106 notation: $\widetilde{T}$
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;p&gt;Suppose &lt;code&gt;$T \in \mathcal{L}(V, W)$&lt;/code&gt;. Define &lt;code&gt;$\widetilde{T}: V/(\text{null } T) \to W$&lt;/code&gt; by&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[\widetilde{T}(v + \text{null } T) = Tv.\]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Think of merging inputs having the same output. These inputs will be the same input in the quotient space &lt;code&gt;$V/(\text{null } T)$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For any &lt;code&gt;$v_1, v_2 \in V$&lt;/code&gt; that &lt;code&gt;$Tv_1 = Tv_2$&lt;/code&gt;, &lt;code&gt;$v_1 + \mathrm{null}\, T$&lt;/code&gt; and &lt;code&gt;$v_2 + \mathrm{null}\, T$&lt;/code&gt; are the same value in &lt;code&gt;$V/(\mathrm{null}\, T)$&lt;/code&gt;. This makes &lt;code&gt;$\widetilde{T}$&lt;/code&gt; injective. Because &lt;code&gt;$\mathrm{range}\,\widetilde{T}=\mathrm{range}\, T$&lt;/code&gt;, &lt;code&gt;$\widetilde{T}$&lt;/code&gt; is also surjective on to &lt;code&gt;$\mathrm{range}\, T$&lt;/code&gt;.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.63 &lt;em&gt;invertibility&lt;/em&gt; $\iff$ &lt;em&gt;injectivity and surjectivity&lt;/em&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
A linear map is invertible if and only if it is injective and surjective.
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;3.63 shows us that &lt;code&gt;$\widetilde{T}$&lt;/code&gt; is invertible, and according to the definition of isomorphic, &lt;code&gt;$V/(\mathrm{null}\, T)$&lt;/code&gt; and &lt;code&gt;$\mathrm{range}\,T$&lt;/code&gt; are isomorphic vector spaces and &lt;code&gt;$\widetilde{T}$&lt;/code&gt; is their isomorphism.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-definition" data-callout-type="definition"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-book"&gt;&lt;/i&gt;
3.69 definition: &lt;em&gt;isomorphism, isomorphic&lt;/em&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;em&gt;isomorphism&lt;/em&gt; is an invertible linear map.&lt;/li&gt;
&lt;li&gt;Two vector spaces are called isomorphic if there is an isomorphism from one vector space onto the other one.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;One of the key uses of &lt;code&gt;$\widetilde{T}$&lt;/code&gt; is demonstrating a canonical isomorphism. For any linear map &lt;code&gt;$T \in \mathcal{L}(V, W)$&lt;/code&gt;, the quotient space &lt;code&gt;$V/(\text{null } T)$&lt;/code&gt; is isomorphic to the image space &lt;code&gt;$\text{range } T$&lt;/code&gt;. This shows that the quotient space &lt;code&gt;$V/(\text{null } T)$&lt;/code&gt; serves as a way to &amp;ldquo;mod out&amp;rdquo; the non-injective part of &lt;code&gt;$T$&lt;/code&gt;.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/math/">Math</category><category domain="https://blog.iany.me/tags/linear-algebra/">Linear Algebra</category></item><item><title>Hosting Static Site on Cloudflare R2</title><link>https://blog.iany.me/2025/10/hosting-static-site-on-cloudflare-r2/</link><pubDate>Wed, 22 Oct 2025 18:46:38 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2025/10/hosting-static-site-on-cloudflare-r2/</guid><description>&lt;p&gt;This post explains how to set up a static site on Cloudflare R2.&lt;/p&gt;
&lt;p&gt;I recently migrated my blog to Cloudflare R2, and the process went smoothly until I encountered R2&amp;rsquo;s lack of native support for rewriting URLs to &lt;code&gt;index.html&lt;/code&gt; files. This post explains how I resolved this issue.&lt;/p&gt;
&lt;h2 id="setting-up"&gt;Setting Up&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create the bucket.&lt;/li&gt;
&lt;li&gt;Add the custom domain &lt;code&gt;blog.iany.me&lt;/code&gt;. To redirect &lt;code&gt;iany.me&lt;/code&gt; and &lt;code&gt;www.iany.me&lt;/code&gt; to the blog, add these as custom domains as well. Since I host the domain in Cloudflare, the DNS record is automatically configured.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="publishing"&gt;Publishing&lt;/h2&gt;
&lt;p&gt;I use &lt;code&gt;rclone&lt;/code&gt; to deploy the site to R2. Below is the GitHub workflow I employ. Noting that the &lt;code&gt;fetch-depth&lt;/code&gt; option for &lt;code&gt;actions/checkout&lt;/code&gt; retrieves the complete repository history. This works with Hugo&amp;rsquo;s &lt;code&gt;--enableGitInfo&lt;/code&gt; flag to accurately determine article creation dates.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;name: Deploy Hugo Site to Cloudflare R2
on:
push:
branches:
- master
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install latest Hugo
run: |
TAG=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest \
| grep '&amp;quot;tag_name&amp;quot;:' \
| head -1 \
| sed -E 's/.*&amp;quot;([^&amp;quot;]+)&amp;quot;.*/\1/')
curl -L &amp;quot;https://github.com/gohugoio/hugo/releases/download/${TAG}/hugo_${TAG#v}_Linux-64bit.tar.gz&amp;quot; \
-o hugo.tar.gz
tar -xzf hugo.tar.gz hugo
sudo mv hugo /usr/local/bin/
hugo version
- name: Install rclone
run: |
curl https://rclone.org/install.sh | sudo bash
- name: Build site with Hugo
run: hugo --minify --enableGitInfo
- name: Configure rclone for Cloudflare R2
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
run: |
rclone config create r2 s3 \
provider Cloudflare \
access_key_id &amp;quot;${R2_ACCESS_KEY_ID}&amp;quot; \
secret_access_key &amp;quot;${R2_SECRET_ACCESS_KEY}&amp;quot; \
endpoint &amp;quot;${R2_ENDPOINT}&amp;quot; \
--quiet
- name: Deploy to Cloudflare R2
env:
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: |
rclone copy public/ r2:&amp;quot;${R2_BUCKET}&amp;quot; \
--checksum \
--no-traverse \
--verbose
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="rules"&gt;Rules&lt;/h2&gt;
&lt;p&gt;Go to the dashboard of the domain &lt;code&gt;iany.me&lt;/code&gt; and go to the section &lt;code&gt;Rules&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="redirect-rules"&gt;Redirect Rules&lt;/h3&gt;
&lt;p&gt;I have added 3 redirect rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When incoming requests have a hostname &lt;code&gt;iany.me&lt;/code&gt; or &lt;code&gt;www.iany.me&lt;/code&gt;, redirect to &lt;code&gt;concat(&amp;quot;https://blog.iany.me&amp;quot;, http.request.uri.path)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Redirect &lt;code&gt;https://blog.iany.me/*/index.html&lt;/code&gt; to &lt;code&gt;https://blog.iany.me/${1}/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Redirect &lt;code&gt;http://*&lt;/code&gt; to &lt;code&gt;https://${1}&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Although the Redirect Rules manual says if multiple rules make the same modification, the last executed rule wins, I have to put rule 2 before 3.&lt;/p&gt;
&lt;h3 id="url-rewrite-rules"&gt;URL Rewrite Rules&lt;/h3&gt;
&lt;p&gt;I added 2 rewrite rules for &lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When the request URL is &lt;code&gt;https://blog.iany.me&lt;/code&gt; or &lt;code&gt;https://blog.iany.me/&lt;/code&gt;, rewrite the URL path to &lt;code&gt;/index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When the request URL matches &lt;code&gt;https://blog.iany.me/*/&lt;/code&gt;, rewrite the path to &lt;code&gt;${1}/index.html&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="cache-rules"&gt;Cache Rules&lt;/h3&gt;
&lt;p&gt;I added 2 cache rules for images and static content:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set cache TTL to 1 month for &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;jpg&lt;/code&gt;, &lt;code&gt;jpeg&lt;/code&gt;, and &lt;code&gt;svg&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;Set cache TTL to 1 year for files in &lt;code&gt;/js&lt;/code&gt;, &lt;code&gt;/fonts&lt;/code&gt;, &lt;code&gt;/css&lt;/code&gt;, and &lt;code&gt;/uploads&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/ci/">CI</category><category domain="https://blog.iany.me/tags/automation/">Automation</category></item><item><title>Explain Atomic Cross-Chain Swaps by Herlihy</title><link>https://blog.iany.me/2025/09/explain-atomic-cross-chain-swaps-by-herlihy/</link><pubDate>Fri, 19 Sep 2025 22:26:46 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2025/09/explain-atomic-cross-chain-swaps-by-herlihy/</guid><description>&lt;p&gt;This article explains the paper [Herlihy, 2018]&lt;sup id="fnref:1"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;1&lt;/span&gt;&lt;/sup&gt; in simple words. In the paper, Herlihy has introduced an effective atomic cross-chain swap protocol in order to exchange assets across multiple blockchains among multiple parties.&lt;/p&gt;
&lt;h2 id="model"&gt;Model&lt;/h2&gt;
&lt;p&gt;A swap can be modeled using a directed graph &lt;code&gt;$(V, A)$&lt;/code&gt;. &lt;code&gt;$V$&lt;/code&gt; is a set of involved users, and &lt;code&gt;$A$&lt;/code&gt; is a set of payments. Each payment is annotated as &lt;code&gt;$(u, v)$&lt;/code&gt; where &lt;code&gt;$u$&lt;/code&gt; is the payer and &lt;code&gt;$v$&lt;/code&gt; is the payee. The Atomic Swap Protocol ensures that when all users follow the protocol, if one payment succeeds, all other payments must succeed.&lt;/p&gt;
&lt;p&gt;For example, following model represents a swap that Alice sends a payment to Bob, and Bob sends a payment to Carol.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\begin{array}{rl}
V &amp;amp;= \{\mathrm{Alice}, \mathrm{Bob}, \mathrm{Carol}\} \\
A &amp;amp;= \{(\mathrm{Alice},\mathrm{Bob}), (\mathrm{Bob}, \mathrm{Carol})\}
\end{array}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img class="kg-image"
alt="Atomic-Swap-Simple-Model.excalidraw"
loading="lazy"
src="https://blog.iany.me/2025/09/explain-atomic-cross-chain-swaps-by-herlihy/atomic-swap-simple-model.excalidraw.svg" /&gt;
&lt;/figure&gt;
&lt;h2 id="simple-protocol"&gt;Simple Protocol&lt;/h2&gt;
&lt;p&gt;When all payments can be connected into a path, like the example above, the protocol is simple and is similar to the payment hops in lightning network.&lt;/p&gt;
&lt;p&gt;A path is annotated as &lt;code&gt;$(u_1, \ldots, u_l)$&lt;/code&gt;, which is consisted of &lt;code&gt;$l-1$&lt;/code&gt; payments: &lt;code&gt;$(u_1, u_2),(u_2, u_3),\ldots,(u_{l-1}, u_l)$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The final payee &lt;code&gt;$u_l$&lt;/code&gt; generates a secret &lt;code&gt;$s$&lt;/code&gt; and sends its hash &lt;code&gt;$H(s)$&lt;/code&gt; to the payer &lt;code&gt;$u_1$&lt;/code&gt;. The payer &lt;code&gt;$u_1$&lt;/code&gt; creates a Hash Time Locked Contract &lt;code&gt;$(H(s), 2(l-1)\Delta)$&lt;/code&gt; that either &lt;code&gt;$u_2$&lt;/code&gt; can get the fund by providing the secret &lt;code&gt;$s$&lt;/code&gt;, or &lt;code&gt;$u_1$&lt;/code&gt; can get the refund when time has elapsed &lt;code&gt;$2(l-1)\Delta$&lt;/code&gt; units. The users &lt;code&gt;$u_i$&lt;/code&gt; from &lt;code&gt;$u_2$&lt;/code&gt; to &lt;code&gt;$u_{l-2}$&lt;/code&gt; creates a Hash Time Locked Contract &lt;code&gt;$(H(s), 2(l-i)\Delta)$&lt;/code&gt; when they confirm that they have received the inbound contract. The payee &lt;code&gt;$u_l$&lt;/code&gt; can settle the deal by providing the secret &lt;code&gt;$s$&lt;/code&gt; to &lt;code&gt;$u_{l-1}$&lt;/code&gt;, and each user uses the received secret &lt;code&gt;$s$&lt;/code&gt; to settle their inbound contract.&lt;/p&gt;
&lt;p&gt;The simple protocol also works when payee and payer are the same user (&lt;code&gt;$u_1 = u_l$&lt;/code&gt;). From engineering points, it should work for most scenarios.&lt;/p&gt;
&lt;h2 id="arbitrary-connected-directed-graph"&gt;Arbitrary Connected Directed Graph&lt;/h2&gt;
&lt;p&gt;The paper also extends the swap protocol to arbitrary connected directed graph. A connected directed graph means for any two user &lt;code&gt;$u, v$&lt;/code&gt;, there are a payment path from &lt;code&gt;$u$&lt;/code&gt; to &lt;code&gt;$v$&lt;/code&gt;, and a payment path from &lt;code&gt;$v$&lt;/code&gt; to &lt;code&gt;$u$&lt;/code&gt;. Such directed graphs contain loops, as in the example below:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\begin{array}{rl}
V = \{&amp;amp;A, B, C, D, E, F\} \\
A = \{ \\
&amp;amp;(A, B), (B, C), (C, A), \\
&amp;amp;(C, D), (D, B), \\
&amp;amp;(D, E), (E, F), (F, D) \\
\} \\
\end{array}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img class="kg-image"
alt="Atomic-Swap-Complex-Connected-Graph.excalidraw"
loading="lazy"
src="https://blog.iany.me/2025/09/explain-atomic-cross-chain-swaps-by-herlihy/atomic-swap-complex-connected-graph.excalidraw.svg" /&gt;
&lt;/figure&gt;
&lt;h3 id="leaders-selection"&gt;Leaders Selection&lt;/h3&gt;
&lt;p&gt;The protocol must select a Feedback Vertex Set of the directed graph as leaders. A feedback vertex set is a subset of &lt;code&gt;$V$&lt;/code&gt; that once removed from the graph, there will be no loops in the graph. For example, &lt;code&gt;$(B, D)$&lt;/code&gt; is a feedback vertex set of the example above. When there are multiple candidates, choose an arbitrary set.&lt;/p&gt;
&lt;p&gt;The leaders generate secrets and publish their hashes. If &lt;code&gt;$(B, D)$&lt;/code&gt; are selected, they should publish &lt;code&gt;$(H(s_B), H(s_D))$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The protocol then move forward in two phases.&lt;/p&gt;
&lt;h3 id="phase-one"&gt;Phase One&lt;/h3&gt;
&lt;p&gt;In the first phase, users offer outbound contracts to counterparty for each payment.&lt;/p&gt;
&lt;p&gt;Leaders must offer contracts first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Publish a contract on every outbound payments, then&lt;/li&gt;
&lt;li&gt;wait until all inbound contracts have been published.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Followers must confirm inbound contracts first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wait until correct inbound contracts have been published, then&lt;/li&gt;
&lt;li&gt;publish a contract on every outbound payments.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The contracts are Hash Time Locked Contracts &lt;code&gt;$(h, t)$&lt;/code&gt; that payee can get the fund using the proof to unlock &lt;code&gt;$h$&lt;/code&gt;, or the payer gets the refund after timeout &lt;code&gt;$t$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For a payment &lt;code&gt;$(u, v)$&lt;/code&gt;, the proof of &lt;code&gt;$h$&lt;/code&gt; is a triple &lt;code&gt;$(s, p, \sigma)$&lt;/code&gt; where&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$p$&lt;/code&gt; is any payment path &lt;code&gt;$(u_0, \ldots, u_k)$&lt;/code&gt; starting from the payee &lt;code&gt;$v$&lt;/code&gt; (&lt;code&gt;$u_0 = v$&lt;/code&gt;) to a leader &lt;code&gt;$u_k$&lt;/code&gt;. The path &lt;code&gt;$p$&lt;/code&gt; can have a length of 0 if &lt;code&gt;$v$&lt;/code&gt; itself is a leader.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$s$&lt;/code&gt; is the secret generated by the ending leader of the path &lt;code&gt;$p$&lt;/code&gt;, and &lt;code&gt;$h = H(s)$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$\sigma$&lt;/code&gt; is a nested signatures of &lt;code&gt;$s$&lt;/code&gt; signed by users in the path &lt;code&gt;$p$&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;The innest signature is &lt;code&gt;$\mathrm{sig}_k = \mathit{sig}(s, u_k)$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For user &lt;code&gt;$u_i$&lt;/code&gt; (&lt;code&gt;$0 \leq i \leq k - 1$&lt;/code&gt;), the signature is &lt;code&gt;$\mathrm{sig}_i = \mathit{sig}(\mathrm{sig}_{i+1}, u_i)$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$\sigma = \mathrm{sig}_0$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hash lock &lt;code&gt;$h$&lt;/code&gt; can be set to &lt;code&gt;$H(s)$&lt;/code&gt; for the shortest &lt;code&gt;$p$&lt;/code&gt;, or just the hashes of all secrets to all unlocking using arbitrary ending leader of the path &lt;code&gt;$p$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The time lock &lt;code&gt;$t$&lt;/code&gt; is &lt;code&gt;$(\mathit{diam}(\mathcal{D}) + |p|)\Delta$&lt;/code&gt;, where&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$\mathit{diam}(\mathcal{D})$&lt;/code&gt; is the longest length of a payment path without duplicated users in the directed graph.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$|p|$&lt;/code&gt; is the length of the path &lt;code&gt;$p$&lt;/code&gt; in the hash lock.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$\Delta$&lt;/code&gt; is a configured time unit constant for the time out.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s see two examples.&lt;/p&gt;
&lt;p&gt;The first example is the payment from a leader: &lt;code&gt;$(B, C)$&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img class="kg-image"
alt="Atomic-Swap-Complex-Connected-Graph-B-C-Contract.excalidraw"
loading="lazy"
src="https://blog.iany.me/2025/09/explain-atomic-cross-chain-swaps-by-herlihy/atomic-swap-complex-connected-graph-b-c-contract.excalidraw.svg" /&gt;
&lt;/figure&gt;
&lt;p&gt;There are three candidate path &lt;code&gt;$p$&lt;/code&gt; from the payee &lt;code&gt;$C$&lt;/code&gt; to a leader either &lt;code&gt;$B$&lt;/code&gt; or &lt;code&gt;$D$&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;$(C, A, B)$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$(C, D)$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$(C, D, B)$&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;$B$&lt;/code&gt; can choose any one, but the shortest one is recommended. Let&amp;rsquo;s use &lt;code&gt;$(C, D)$&lt;/code&gt;. &lt;code&gt;$B$&lt;/code&gt; can publish the contract because he/she is the leader.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The hash lock for &lt;code&gt;$(B, C)$&lt;/code&gt; is &lt;code&gt;$H(s_D)$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The time lock is &lt;code&gt;$6\Delta$&lt;/code&gt;, because the length of the longest path &lt;code&gt;$(A, B, C, D, E, F)$&lt;/code&gt; is 5, and the length of &lt;code&gt;$(C, D)$&lt;/code&gt; is 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second example is the payment from a follower: &lt;code&gt;$(C, A)$&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img class="kg-image"
alt="Atomic-Swap-Complex-Connected-Graph-C-a-Contract.excalidraw"
loading="lazy"
src="https://blog.iany.me/2025/09/explain-atomic-cross-chain-swaps-by-herlihy/atomic-swap-complex-connected-graph-c-a-contract.excalidraw.svg" /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;$C$&lt;/code&gt; can choose the path &lt;code&gt;$(A, B)$&lt;/code&gt; and publish the contract:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The hash lock is &lt;code&gt;$H(s_B)$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The time lock is &lt;code&gt;$6\Delta$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="phase-two"&gt;Phase Two&lt;/h3&gt;
&lt;p&gt;In phase two, leaders can unlock the inbound contracts because they know all secrets. For example, the leader &lt;code&gt;$B$&lt;/code&gt; can unlock the inbound payment &lt;code&gt;$(A, B)$&lt;/code&gt; by offering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The path &lt;code&gt;$p = (B)$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The secret &lt;code&gt;$s_B$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The signature &lt;code&gt;$sig(s_B, B)$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Followers must trace outbound contracts unlocking events. For example, &lt;code&gt;$A$&lt;/code&gt; can unlock the contract for &lt;code&gt;$(C, A)$&lt;/code&gt; after receiving the unlocking event above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The path &lt;code&gt;$p = (A, B)$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The secret &lt;code&gt;$s_B$&lt;/code&gt; since B has published it when unlocking &lt;code&gt;$(A, B)$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The signature &lt;code&gt;$sig(sig(s_B, B), A)$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Herlihy, M. (2018). Atomic Cross-Chain Swaps (No. arXiv:1801.09515). arXiv. &lt;a href="https://doi.org/10.48550/arXiv.1801.09515"&gt;https://doi.org/10.48550/arXiv.1801.09515&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/blockchain/">Blockchain</category><category domain="https://blog.iany.me/tags/cryptography/">Cryptography</category><category domain="https://blog.iany.me/tags/distributed-system/">Distributed System</category></item><item><title>Envrc Alternative for PowerShell in Windows</title><link>https://blog.iany.me/2024/04/envrc-alternative-for-powershell-in-windows/</link><pubDate>Sat, 06 Apr 2024 08:55:34 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2024/04/envrc-alternative-for-powershell-in-windows/</guid><description>&lt;p&gt;This post introduces a solution for automatically setting up and tearing down shell environments for PowerShell in Windows. It is proposed as a potential alternative to the bash-based tool &lt;a href="https://github.com/direnv/direnv"&gt;direnv&lt;/a&gt;, which, while effective at loading &lt;code&gt;.envrc&lt;/code&gt; files in the current or nearest ancestor directory, has limited compatibility with PowerShell in Windows.&lt;/p&gt;
&lt;h2 id="envrcps1"&gt;.envrc.ps1&lt;/h2&gt;
&lt;p&gt;The basic idea is creating a file &lt;code&gt;.envrc.ps1&lt;/code&gt; with two entries: setup and teardown. The setup entry is responsible for setting up the environment up entering the directory, and the teardown entry is used when leaving it.&lt;/p&gt;
&lt;p&gt;PowerShell allows code to be executed from a file using the dot command. This means that the entire file can be used as the setup entry. For the teardown entry, a function can be defined within the same file, and I use the function name &lt;code&gt;down&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The layout of the file &lt;code&gt;.envrc.ps1&lt;/code&gt; looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;# setup
$env:GH_TOKEN = &amp;quot;secret&amp;quot;
# teardown
function global:down {
$env:GH_TOKEN = &amp;quot;&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;global:&lt;/code&gt; scope before the function name is necessary when this file is loaded in the hook, as I will do later.&lt;/p&gt;
&lt;p&gt;The corresponding commands for setup and teardown entries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setup: &lt;code&gt;. .envrc.ps1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Teardown: &lt;code&gt;down&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hook"&gt;Hook&lt;/h2&gt;
&lt;p&gt;To hook the setup and teardown entries, I reference &lt;a href="https://github.com/PowerShell/PowerShell/issues/14484#issuecomment-1731647083"&gt;this example&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;using namespace System;
using namespace System.Management.Automation;
$hook = [EventHandler[LocationChangedEventArgs]] {
param([object] $source, [LocationChangedEventArgs] $eventArgs)
end {
# 1. `down` for $eventArgs.OldPath
# 2. `. .envrc.ps1` for $eventArgs.NewPath
}
};
$currentAction = $ExecutionContext.SessionState.InvokeCommand.LocationChangedAction;
if ($currentAction) {
$ExecutionContext.SessionState.InvokeCommand.LocationChangedAction = [Delegate]::Combine($currentAction, $hook);
} else {
$ExecutionContext.SessionState.InvokeCommand.LocationChangedAction = $hook;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the code snippet, PowerShell 7 or later is required. You can install it from the &lt;a href="https://github.com/PowerShell/PowerShell"&gt;repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is the algorithm for identifying setup and teardown entries:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the nearest &lt;code&gt;.envrc.ps1&lt;/code&gt; for the old path.&lt;/li&gt;
&lt;li&gt;Find the nearest &lt;code&gt;.envrc.ps1&lt;/code&gt; for the new path.&lt;/li&gt;
&lt;li&gt;If the two paths are the same, skip the next step.&lt;/li&gt;
&lt;li&gt;If they differ, invoke the &lt;code&gt;down&lt;/code&gt; function and remove it if it exists. Then, if found, load the &lt;code&gt;.envrc.ps1&lt;/code&gt; file for the new path.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The full code example to be added into PowerShell &lt;code&gt;$PROFILE&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;using namespace System;
using namespace System.Management.Automation;
# existing PowerShell profile contents
# skip when version is less than 7
if ($PSVersionTable.PSVersion.Major -lt 7) {
return
}
# find the .envrc.ps1 in the currenct directory or the nearest ancestor directory.
function Find-NearestEnvrc {
param (
[Parameter(Mandatory=$true)]
[string]$StartDir
)
$currentDir = Resolve-Path $StartDir
while ($currentDir -ne &amp;quot;&amp;quot;) {
$envrcPath = Join-Path $currentDir &amp;quot;.envrc.ps1&amp;quot;
if (Test-Path $envrcPath) {
return $envrcPath
}
$currentDir = Split-Path $currentDir -Parent
}
return &amp;quot;&amp;quot;
}
# hook the setup and teardown entries
$hook = [EventHandler[LocationChangedEventArgs]] {
param([object] $source, [LocationChangedEventArgs] $eventArgs)
end {
$oldEnvrc = Find-NearestEnvrc $eventArgs.OldPath
$newEnvrc = Find-NearestEnvrc $eventArgs.NewPath
if ($oldEnvrc -ne $newEnvrc) {
Get-Command down -ErrorAction SilentlyContinu
if (Get-Command down -ErrorAction SilentlyContinu) {
down
Remove-Item Function:down
}
if ($newEnvrc -ne &amp;quot;&amp;quot;) {
. $newEnvrc
}
}
}
};
$currentAction = $ExecutionContext.SessionState.InvokeCommand.LocationChangedAction;
if ($currentAction) {
$ExecutionContext.SessionState.InvokeCommand.LocationChangedAction = [Delegate]::Combine($currentAction, $hook);
} else {
$ExecutionContext.SessionState.InvokeCommand.LocationChangedAction = $hook;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="example-usage"&gt;Example Usage&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;# setup
# set environment variable
$env:GH_TOKEN = &amp;quot;secret&amp;quot;
# load python virtual env
. .venv\Scripts\Activate.ps1
# add a helper function
function global:build {
cargo build
}
# teardown
function global:down {
# unset environment variable
$env:GH_TOKEN = &amp;quot;&amp;quot;
# unload python virtual env
deactivate
# remove the helper function
Remove-Item Function:build
}
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/environment-variables/">Environment Variables</category><category domain="https://blog.iany.me/tags/powershell/">PowerShell</category><category domain="https://blog.iany.me/tags/windows/">Windows</category></item><item><title>How to Verify JoyID WebAuthn Signature</title><link>https://blog.iany.me/2023/12/how-to-verify-joyid-webauthn-signature/</link><pubDate>Sun, 17 Dec 2023 17:35:33 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2023/12/how-to-verify-joyid-webauthn-signature/</guid><description>&lt;p&gt;&lt;a href="https://docs.joy.id/guide"&gt;JoyID&lt;/a&gt; is a multichain, cross-platform, passwordless and mnemonic-free wallet solution based on FIDO WebAuthn protocol and Nervos CKB.&lt;/p&gt;
&lt;p&gt;This post shows how to verify the signature from the method &lt;a href="https://docs.joy.id/guide/ckb/sign-message"&gt;signChallenge&lt;/a&gt; of the &lt;code&gt;@joyid/ckb&lt;/code&gt; package. The method reference page has a demo. I use the demo to obtain an example response then verify the response using the OpenSSL command line and the Python library &lt;a href="https://pycryptodome.readthedocs.io/en/latest/src/introduction.html"&gt;PyCryptodome&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The JoyID follows the WebAuthn specification and employs secp256r1 for signing. Although the guide references &lt;a href="https://www.w3.org/TR/webauthn-2/#sctn-op-get-assertion"&gt;section 6.3.3&lt;/a&gt; of the WebAuthn specification, titled &amp;ldquo;The authenticatorGetAssertion Operation&amp;rdquo;, I discovered that the example in &lt;a href="https://github.com/duo-labs/py_webauthn/blob/master/webauthn/authentication/verify_authentication_response.py"&gt;this repository&lt;/a&gt; provided me much more helps.&lt;/p&gt;
&lt;h2 id="the-response-parsing"&gt;The Response Parsing&lt;/h2&gt;
&lt;p&gt;This is the example I obtained from the demo.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
&amp;quot;signature&amp;quot;: &amp;quot;MEUCICF25qdO6nLreEoBHnyaw-9R6XFHbIu-NwsAI53t016qAiEAgmhlwTEMxoWxKj79R1rUkB_6nrhJfws82DqHkY_HnqQ&amp;quot;,
&amp;quot;message&amp;quot;: &amp;quot;K4sF4fAwPvuJj-TW3mARmMenuGSrvmohxzsueH4YfFIFAAAAAHsidHlwZSI6IndlYmF1dGhuLmdldCIsImNoYWxsZW5nZSI6IlUybG5iaUIwYUdseklHWnZjaUJ0WlEiLCJvcmlnaW4iOiJodHRwczovL3Rlc3RuZXQuam95aWQuZGV2IiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0&amp;quot;,
&amp;quot;challenge&amp;quot;: &amp;quot;Sign this for me&amp;quot;,
&amp;quot;alg&amp;quot;: -7,
&amp;quot;pubkey&amp;quot;: &amp;quot;3538dfd53ad93d2e0a6e7f470295dcd71057d825e1f87229e5afe2a906aa7cfc099fdfa04442dac33548b6988af8af58d2052529088f7b73ef00800f7fbcddb3&amp;quot;,
&amp;quot;keyType&amp;quot;: &amp;quot;main_key&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="pubkey"&gt;pubkey&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;pubkey&lt;/code&gt; field represents the uncompressed public key concatenating two 32-byte integers in hex. PyCryptodome can import the key by prepending the flag &lt;code&gt;0x04&lt;/code&gt;. OpenSSL uses PEM to encode keys, and PyCryptodome can help here to export the key in PEM format.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from Crypto.PublicKey import ECC
pubkey_raw_hex = &amp;quot;3538dfd53ad93d2e0a6e7f470295dcd71057d825e1f87229e5afe2a906aa7cfc099fdfa04442dac33548b6988af8af58d2052529088f7b73ef00800f7fbcddb3&amp;quot;
pubkey = ECC.import_key(bytes.fromhex(&amp;quot;04&amp;quot; + pubkey_raw_hex), curve_name=&amp;quot;secp256r1&amp;quot;)
with open(&amp;quot;pubkey.pem&amp;quot;, &amp;quot;wt&amp;quot;) as pemfile:
pemfile.write(pubkey.export_key(format=&amp;quot;PEM&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Double check the key using OpenSSL:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell-session"&gt;$ openssl ec -text -inform PEM -in pubkey.pem -pubin
...
Public-Key: (256 bit)
pub:
04:35:38:df:d5:3a:d9:3d:2e:0a:6e:7f:47:02:95:
dc:d7:10:57:d8:25:e1:f8:72:29:e5:af:e2:a9:06:
aa:7c:fc:09:9f:df:a0:44:42:da:c3:35:48:b6:98:
8a:f8:af:58:d2:05:25:29:08:8f:7b:73:ef:00:80:
0f:7f:bc:dd:b3
ASN1 OID: prime256v1
NIST CURVE: P-256
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="message"&gt;message&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;message&lt;/code&gt; is a binary encoded by base64 &lt;a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5"&gt;RFC 4648 §5&lt;/a&gt; without the equal sign (&lt;code&gt;=&lt;/code&gt;) paddings. Many base64 tools and libraries require padding equal sign (&lt;code&gt;=&lt;/code&gt;) in the end of the string to make the length multiple of 4. The &lt;code&gt;message&lt;/code&gt; in the example response has a length 351, which requires one &lt;code&gt;=&lt;/code&gt; padding. A trick is always padding two equals at the end of the string before decoding.&lt;/p&gt;
&lt;p&gt;The first 37 bytes in &lt;code&gt;message&lt;/code&gt; are authenticator data, and the following bytes are client data in JSON.&lt;/p&gt;
&lt;p&gt;The section &lt;a href="https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data"&gt;section 6.1&lt;/a&gt; in the WebAuthn specification defines the layout of the authenticator data.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rpIdHash&lt;/code&gt;, 32 bytes: the sha256 checksum of the text &lt;code&gt;testnet.joyid.dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flags&lt;/code&gt;, 1 byte: &lt;code&gt;0x05&lt;/code&gt; in JoyID&lt;/li&gt;
&lt;li&gt;&lt;code&gt;signCount&lt;/code&gt;, 4 bytes: all zeros&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;base64 -d &amp;lt;&amp;lt;&amp;lt;'
K4sF4fAwPvuJj-TW3mARmMenuGSrvmohxzsueH4YfFIFAAAAAHsidHlwZSI6Indl
YmF1dGhuLmdldCIsImNoYWxsZW5nZSI6IlUybG5iaUIwYUdseklHWnZjaUJ0WlEi
LCJvcmlnaW4iOiJodHRwczovL3Rlc3RuZXQuam95aWQuZGV2IiwiY3Jvc3NPcmln
aW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90
IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUg
aHR0cHM6Ly9nb28uZ2wveWFiUGV4In0=' |
dd bs=1 count=37 2&amp;gt;/dev/null |
xxd
#=&amp;gt; 00000000: 2b8b 05e1 f030 3efb 898f e4d6 de60 1198
#=&amp;gt; 00000010: c7a7 b864 abbe 6a21 c73b 2e78 7e18 7c52
#=&amp;gt; 00000020: 0500 0000 00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the first two lines with the sha256 checksum:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;echo -n 'testnet.joyid.dev' | sha256sum
#=&amp;gt; 2b8b05e1f0303efb898fe4d6de601198c7a7b864abbe6a21c73b2e787e187c52 -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client data JSON looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;base64 -d &amp;lt;&amp;lt;&amp;lt;'
K4sF4fAwPvuJj-TW3mARmMenuGSrvmohxzsueH4YfFIFAAAAAHsidHlwZSI6Indl
YmF1dGhuLmdldCIsImNoYWxsZW5nZSI6IlUybG5iaUIwYUdseklHWnZjaUJ0WlEi
LCJvcmlnaW4iOiJodHRwczovL3Rlc3RuZXQuam95aWQuZGV2IiwiY3Jvc3NPcmln
aW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90
IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUg
aHR0cHM6Ly9nb28uZ2wveWFiUGV4In0=' |
dd bs=1 skip=37 2&amp;gt;/dev/null |
jq
{
&amp;quot;type&amp;quot;: &amp;quot;webauthn.get&amp;quot;,
&amp;quot;challenge&amp;quot;: &amp;quot;U2lnbiB0aGlzIGZvciBtZQ&amp;quot;,
&amp;quot;origin&amp;quot;: &amp;quot;https://testnet.joyid.dev&amp;quot;,
&amp;quot;crossOrigin&amp;quot;: false,
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;challenge&lt;/code&gt; field. It is the parameter passed to &lt;code&gt;signChallenge&lt;/code&gt;, in base64.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;base64 -d &amp;lt;&amp;lt;&amp;lt;'U2lnbiB0aGlzIGZvciBtZQ=='
#=&amp;gt; Sign this for me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Attention that message is not the binary to be signed. According to the Figure 4, Generating an assertion signature, in &lt;a href="https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data"&gt;the WebAuthn specification&lt;/a&gt;, the binary to be signed is a concatenation of the authenticator data and the sha256 checksum of the client data JSON.&lt;/p&gt;
&lt;p&gt;The following code shows how to prepare the message to sign and save it into the file &lt;code&gt;message.bin&lt;/code&gt;. Attention that base64 must use the alternative keys &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt; to replace &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; respectively.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-attention" data-callout-type="attention"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-exclamation-triangle"&gt;&lt;/i&gt;
Attention
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
To decode base64 &amp;ldquo;RFC 4648 §5&amp;rdquo; in python, use either &lt;code&gt;base64.b64decode(s, altchars=&amp;quot;-_&amp;quot;)&lt;/code&gt; or &lt;code&gt;binascii.urlsafe_b64decode(s)&lt;/code&gt;.
&lt;/div&gt;
&lt;/details&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import base64
from Crypto.Hash import SHA256
message_bin = base64.urlsafe_b64decode(
&amp;quot;K4sF4fAwPvuJj-TW3mARmMenuGSrvmohxzsueH4YfFIFAAAAAHsidHlwZSI6Indl&amp;quot;
&amp;quot;YmF1dGhuLmdldCIsImNoYWxsZW5nZSI6IlUybG5iaUIwYUdseklHWnZjaUJ0WlEi&amp;quot;
&amp;quot;LCJvcmlnaW4iOiJodHRwczovL3Rlc3RuZXQuam95aWQuZGV2IiwiY3Jvc3NPcmln&amp;quot;
&amp;quot;aW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90&amp;quot;
&amp;quot;IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUg&amp;quot;
&amp;quot;aHR0cHM6Ly9nb28uZ2wveWFiUGV4In0==&amp;quot;,
)
authenticator_data = message_bin[:37]
client_data = message_bin[37:]
message_to_sign = authenticator_data + SHA256.new(client_data).digest()
with open(&amp;quot;message.bin&amp;quot;, &amp;quot;wb&amp;quot;) as fout:
fout.write(message_to_sign)
&lt;/code&gt;&lt;/pre&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-attention" data-callout-type="attention"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-exclamation-triangle"&gt;&lt;/i&gt;
Attention
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
The &lt;code&gt;message&lt;/code&gt; in the response is not the binary to be signed. Instead, the binary to be signed is a concatenation of the authenticator data and the sha256 checksum of the client data JSON.
&lt;/div&gt;
&lt;/details&gt;
&lt;h3 id="signature"&gt;signature&lt;/h3&gt;
&lt;p&gt;The field signature are two 32-byte integers first encoded in &lt;a href="https://wiki.openssl.org/index.php/DER"&gt;DER&lt;/a&gt;, then base64 &lt;a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5"&gt;RFC 4648 §5&lt;/a&gt; without the equal sign (&lt;code&gt;=&lt;/code&gt;) paddings.&lt;/p&gt;
&lt;p&gt;Many base64 tools and libraries require padding equal sign (&lt;code&gt;=&lt;/code&gt;) in the end of the string to make the length multiple of 4. The signature in the example response has a length 95, which requires one &lt;code&gt;=&lt;/code&gt; padding.&lt;/p&gt;
&lt;p&gt;OpenSSL also stores signature in DER, let&amp;rsquo;s save one in the file &lt;code&gt;signature.der&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;base64 -d &amp;lt;&amp;lt;&amp;lt;&amp;quot;MEUCICF25qdO6nLreEoBHnyaw-9R6XFHbIu-NwsAI53t016qAiEAgmhlwTEMxoWxKj79R1rUkB_6nrhJfws82DqHkY_HnqQ=&amp;quot; &amp;gt; signature.der
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command &lt;code&gt;openssl asn1parse&lt;/code&gt; can parse the file &lt;code&gt;signature.der&lt;/code&gt; in the DER format.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;openssl asn1parse -dump -inform DER -in signature.der
# Output =&amp;gt;
# 0:d=0 hl=2 l= 69 cons: SEQUENCE
# 2:d=1 hl=2 l= 32 prim: INTEGER :2176E6A74EEA72EB784A011E7C9AC3EF51E971476C8BBE370B00239DEDD35EAA
# 36:d=1 hl=2 l= 33 prim: INTEGER :826865C1310CC685B12A3EFD475AD4901FFA9EB8497F0B3CD83A87918FC79EA4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PyCryptodome expects the signature of 64 bytes for two 32-byte integers. Following code uses the &lt;code&gt;asn1&lt;/code&gt; module to extract the raw signature from the DER binary.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import base64
from Crypto.Util.asn1 import DerSequence
signature_der = base64.urlsafe_b64decode(
&amp;quot;MEUCICF25qdO6nLreEoBHnyaw-9R6XFHbIu-NwsAI53t016qAiEAgmhlwTEMxoWx&amp;quot;
&amp;quot;Kj79R1rUkB_6nrhJfws82DqHkY_HnqQ=&amp;quot;,
)
signature_seq = DerSequence()
signature_seq.decode(signature_der)
print(signature_seq[0].to_bytes(32).hex())
# =&amp;gt; 2176e6a74eea72eb784a011e7c9ac3ef51e971476c8bbe370b00239dedd35eaa
print(signature_seq[1].to_bytes(32).hex())
# =&amp;gt; 826865c1310cc685b12a3efd475ad4901ffa9eb8497f0b3cd83a87918fc79ea4
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="verifying"&gt;Verifying&lt;/h2&gt;
&lt;p&gt;PyCryptodome:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from Crypto.Hash import SHA256
from Crypto.Signature import DSS
DSS.new(pubkey, &amp;quot;fips-186-3&amp;quot;).verify(SHA256.new(message_to_sign), signature)
print(&amp;quot;Verified OK&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OpenSSL:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;openssl dgst -sha256 -verify pubkey.pem -signature signature.der message.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;details class="kg-card kg-callout kg-callout-code" data-callout-type="code"&gt;
&lt;summary class="kg-callout-title"&gt;
&lt;i class="fas fa-chevron-right"&gt;&lt;/i&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-code"&gt;&lt;/i&gt;
Full Python code (&lt;a href="https://gist.github.com/doitian/b1f5c60203e9dbaffccff7d0920d9529"&gt;Gist&lt;/a&gt;)
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import base64
from Crypto.Hash import SHA256
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from Crypto.Util.asn1 import DerSequence
response = {
&amp;quot;signature&amp;quot;: &amp;quot;MEUCICF25qdO6nLreEoBHnyaw-9R6XFHbIu-NwsAI53t016qAiEAgmhlwTEMxoWx&amp;quot;
&amp;quot;Kj79R1rUkB_6nrhJfws82DqHkY_HnqQ&amp;quot;,
&amp;quot;message&amp;quot;: &amp;quot;K4sF4fAwPvuJj-TW3mARmMenuGSrvmohxzsueH4YfFIFAAAAAHsidHlwZSI6IndlYmF1dGhuLmdldCIsImNoYWxsZW5nZSI6IlUybG5iaUIwYUdseklHWnZjaUJ0WlEiLCJvcmlnaW4iOiJodHRwczovL3Rlc3RuZXQuam95aWQuZGV2IiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0&amp;quot;,
&amp;quot;challenge&amp;quot;: &amp;quot;Sign this for me&amp;quot;,
&amp;quot;alg&amp;quot;: -7,
&amp;quot;pubkey&amp;quot;: &amp;quot;3538dfd53ad93d2e0a6e7f470295dcd71057d825e1f87229e5afe2a906aa7cfc099fdfa04442dac33548b6988af8af58d2052529088f7b73ef00800f7fbcddb3&amp;quot;,
&amp;quot;keyType&amp;quot;: &amp;quot;main_key&amp;quot;,
}
pubkey = ECC.import_key(
bytes.fromhex(&amp;quot;04&amp;quot; + response[&amp;quot;pubkey&amp;quot;]),
curve_name=&amp;quot;secp256r1&amp;quot;,
)
with open(&amp;quot;pubkey.pem&amp;quot;, &amp;quot;wt&amp;quot;) as fout:
fout.write(pubkey.export_key(format=&amp;quot;PEM&amp;quot;))
message_bin = base64.urlsafe_b64decode(response[&amp;quot;message&amp;quot;] + &amp;quot;==&amp;quot;)
authenticator_data = message_bin[:37]
client_data = message_bin[37:]
# https://github.com/duo-labs/py_webauthn/blob/master/webauthn/authentication/verify_authentication_response.py
message_to_sign = authenticator_data + SHA256.new(client_data).digest()
with open(&amp;quot;message.bin&amp;quot;, &amp;quot;wb&amp;quot;) as fout:
fout.write(message_to_sign)
signature_der = base64.urlsafe_b64decode(response[&amp;quot;signature&amp;quot;] + &amp;quot;==&amp;quot;)
with open(&amp;quot;signature.der&amp;quot;, &amp;quot;wb&amp;quot;) as fout:
fout.write(signature_der)
signature_seq = DerSequence()
signature_seq.decode(signature_der)
signature = signature_seq[0].to_bytes(32) + signature_seq[1].to_bytes(32)
DSS.new(pubkey, &amp;quot;fips-186-3&amp;quot;).verify(SHA256.new(message_to_sign), signature)
print(&amp;quot;Verified OK&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/cryptography/">Cryptography</category><category domain="https://blog.iany.me/tags/javascript/">JavaScript</category><category domain="https://blog.iany.me/tags/webauthn/">Webauthn</category></item><item><title>Renaming Browser Tab Names</title><link>https://blog.iany.me/2023/07/renaming-browser-tab-names/</link><pubDate>Fri, 21 Jul 2023 20:31:59 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2023/07/renaming-browser-tab-names/</guid><description>&lt;p&gt;Renaming browser tab names may seem like a simple task, but it can actually be quite challenging.&lt;/p&gt;
&lt;p&gt;Most browsers sync the tab name with the web page title. Therefore, it seems simple to set tab name by setting &lt;code&gt;document.title&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;document.title = &amp;quot;Custom Tab Name&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, many web pages use JavaScript to alter the page title, which would override the custom name. While &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"&gt;&lt;code&gt;Object.defineProperty&lt;/code&gt;&lt;/a&gt; in JavaScript can override the property setter function, it&amp;rsquo;s not possible to access the original setter function for &lt;code&gt;document.title&lt;/code&gt;. Fortunately, browsers sync page titles with the first &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag, so setting its content acts as a workaround to set the page title.&lt;/p&gt;
&lt;p&gt;Here is the bookmark for renaming a tab. I also add &lt;code&gt;%t&lt;/code&gt; as the token for the original page tab title. This is handy such as adding a custom prefix with &lt;code&gt;Work: %t&lt;/code&gt;, or restoring the original title without reloading the page by using &lt;code&gt;%t&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;// Rename the document title using a bookmarklet.
//
// As a user,
// - Once I have renamed the tab, it should not be overwritten.
// - I can include the token %t as placeholder for the real title.
// - To restore, I can rename the tab to %t
//
// Install: Copy the code to
//
// https://caiorss.github.io/bookmarklet-maker/
//
// and generate the bookmarklet.
// Init only once
if (!(&amp;quot;_renameTitle&amp;quot; in document)) {
// Force creating the title tag.
document.title = document.title || &amp;quot;&amp;quot;;
const titleEl = document.getElementsByTagName(&amp;quot;title&amp;quot;)[0];
const titleTokenRegex = /%t/g;
// Remembers the real title
let titleWithoutRenaming = document.title;
// User set title
let titleWithRenaming = &amp;quot;&amp;quot;;
// Rename the document title to v.
// If v contains the token %t, replace all occurences to the
// real title.
document._renameTitle = (v) =&amp;gt; {
titleWithRenaming = v;
titleEl.innerText = v.replace(titleTokenRegex, titleWithoutRenaming);
};
Object.defineProperty(document, &amp;quot;title&amp;quot;, {
// Other code will use document.title setter to change the title.
//
// Remember the new value as the real title but still use the
// title set by user.
set: (v) =&amp;gt; {
titleWithoutRenaming = v;
// Once document has defined the title property, its value is
// not synchronized to the tab name.
//
// Here uses a workaround to set the title tag content.
titleEl.innerText = titleWithRenaming.replace(titleTokenRegex, v);
},
get: () =&amp;gt; titleEl.innerText,
});
}
const title = prompt(
&amp;quot;Rename tab (Use token %t for original title)&amp;quot;,
document.title
);
// title is null when user cancel the dialog.
if (title !== null) {
document._renameTitle(title);
}
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/browser/">Browser</category><category domain="https://blog.iany.me/tags/javascript/">JavaScript</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category></item><item><title>Transforming Markdown to Attractive PDFs: A Guide to Using Pandoc with Xelatex</title><link>https://blog.iany.me/2023/04/transforming-markdown-to-attractive-pdfs-a-guide-to-using-pandoc-with-xelatex/</link><pubDate>Sat, 29 Apr 2023 21:43:02 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2023/04/transforming-markdown-to-attractive-pdfs-a-guide-to-using-pandoc-with-xelatex/</guid><description>&lt;p&gt;I used to read lengthy and complex articles in the PDF format. Despite the abundance of PDF exporting options, the resulting files often appear unappealing. However, there is a solution. With the pandoc tool and the xelatex backend, you can transform Markdown files into aesthetically pleasing PDFs. In this tutorial, I will guide you through the steps of using pandoc with xelatex and share the lessons I&amp;rsquo;ve learned.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;Ubuntu users can install the package &lt;code&gt;texlive-xetex&lt;/code&gt; and &lt;code&gt;pandoc&lt;/code&gt;, In macOS, I recommend a minimal installation:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;brew install librsvg pandoc
brew install --cask basictex
sudo tlmgr update --self
sudo tlmgr update --all
for pkg in texliveonfly xelatex adjustbox tcolorbox collectbox ucs environ \
trimspaces titling enumitem rsfs xecjk fvextra svg transparent; do
sudo tlmgr install $pkg
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The library &lt;code&gt;librsvg&lt;/code&gt; is required to support SVG images.&lt;/p&gt;
&lt;p&gt;Verify the installation by converting a simple markdown file.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;echo &amp;quot;# Hello World&amp;quot; &amp;gt; test.md
pandoc --pdf-engine=xelatex -i test.md -o test.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="tips"&gt;Tips&lt;/h2&gt;
&lt;p&gt;In LaTeX, the document class serves as the template for the basic page styles.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a fan of &lt;a href="https://ctan.org/pkg/koma-script?lang=en"&gt;koma-script&lt;/a&gt;, and I will choose between &lt;em&gt;scrreprt&lt;/em&gt; and &lt;em&gt;scrbook&lt;/em&gt; depending whether the file to create is more like a report or a book. I recommend try them and the standard classes like &lt;em&gt;report&lt;/em&gt; and &lt;em&gt;book&lt;/em&gt;, then choose the one that best suites your preferences.&lt;/p&gt;
&lt;p&gt;To customize the document class in pandoc, simply set the variable &lt;code&gt;documentclass&lt;/code&gt;. Other &lt;a href="https://pandoc.org/MANUAL.html#variables-for-latex"&gt;variables&lt;/a&gt; are available for controlling the LaTeX style. I use some of them to define my default style.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;DOCUMENTCLASS=&amp;quot;${DOCUMENTCLASS:-scrreprt}&amp;quot;
MAINFONT=&amp;quot;${MAINFONT:-Helvetica}&amp;quot;
MONOFONT=&amp;quot;${MONOFONT:-Cartograph CF}&amp;quot;
pandoc --pdf-engine=xelatex \
--variable fontsize=12pt \
--variable linestretch=1.5 \
--variable geometry=a4paper \
--variable documentclass=&amp;quot;${DOCUMENTCLASS}&amp;quot; \
--variable mainfont=&amp;quot;${MAINFONT}&amp;quot; \
--variable monofont=&amp;quot;${MONOFONT}&amp;quot; \
-i test.md -o test.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="gotchas"&gt;Gotchas&lt;/h2&gt;
&lt;h3 id="cjk"&gt;CJK&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CJK&lt;/strong&gt; is a collective term for the Chinese, Japanese, and Korean languages. When &lt;code&gt;CJKmainfont&lt;/code&gt; is set, pandoc handles CJK characters with the &lt;code&gt;xecjk&lt;/code&gt; package.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;pandoc --variable CJKmainfont=&amp;quot;Noto Serif CJK SC&amp;quot; ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My recommendation is to only set &lt;code&gt;CJKmainfont&lt;/code&gt; when needed to avoid messing up the quotation marks display. Where the article contains a mixture of English and Asian characters resulting in strange looking quotation marks &lt;code&gt;'&amp;quot;“”‘’&lt;/code&gt;, consider trying the following solution.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a file &lt;code&gt;cjk.latex&lt;/code&gt; to force English quotation marks.
&lt;pre&gt;&lt;code class="language-latex"&gt;% cjk.tex
\AtBeginDocument{%
\XeTeXcharclass`^^^^2019=0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Include this file via the pandoc argument &lt;code&gt;-H&lt;/code&gt; such as &lt;code&gt;pandoc -H cjk.tex&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The figure below shows the results from the same markdown file on my MacBook. However, when I test this in Ubuntu, it seems there are no such issues.&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Fix CJK Quotation Marks" class="kg-image" loading="lazy" src="https://blog.iany.me/2023/04/transforming-markdown-to-attractive-pdfs-a-guide-to-using-pandoc-with-xelatex/pandoc-pdf-quotation-marks_hu_3473858a480b8700.png" srcset="https://blog.iany.me/2023/04/transforming-markdown-to-attractive-pdfs-a-guide-to-using-pandoc-with-xelatex/pandoc-pdf-quotation-marks_hu_e860b9b6beebbcbe.png 400w, https://blog.iany.me/2023/04/transforming-markdown-to-attractive-pdfs-a-guide-to-using-pandoc-with-xelatex/pandoc-pdf-quotation-marks_hu_7824d52cc0ee7640.png 800w, https://blog.iany.me/2023/04/transforming-markdown-to-attractive-pdfs-a-guide-to-using-pandoc-with-xelatex/pandoc-pdf-quotation-marks_hu_3473858a480b8700.png 1061w" sizes="(max-width: 800px) 100vw, 1061px" /&gt;
&lt;figcaption &gt;Fix CJK Quotation Marks&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is the input markdown file used in the example.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;English's &amp;quot;straight quotations&amp;quot; and this’s “curly quotations”.
中文的&amp;quot;直引号&amp;quot;和“弯引号”。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="code-block-syntax-highlight"&gt;Code Block Syntax Highlight&lt;/h3&gt;
&lt;p&gt;Pandoc does not wrap lines in code blocks by default. It truncated the lines which are too long to fit on the page.&lt;/p&gt;
&lt;p&gt;To solve the problem mentioned above, I followed a solution on &lt;a href="https://github.com/jgm/pandoc/issues/4302#issuecomment-360799891"&gt;GitHub&lt;/a&gt; suggested by jannick0. It involves the creation of a file that contains the LaTeX snippets below. These snippets can then be included by using the pandoc argument &lt;code&gt;-H highlighting.tex&lt;/code&gt;. Additionally, I added a border around the code block.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-file" data-callout-type="file"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-file"&gt;&lt;/i&gt;
&lt;code&gt;highlighting.tex&lt;/code&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-latex"&gt;% use this file via pandoc -H highlighting.tex
\usepackage{fvextra}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{breaklines,breaknonspaceingroup,breakanywhere,frame=single,framesep=8pt,rulecolor=\color[HTML]{aaaaaa},commandchars=\\\{\}}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;As I &lt;a href="https://github.com/jgm/pandoc/issues/4302#issuecomment-1508595755"&gt;replied in the thread&lt;/a&gt;, this solution does not work for code blocks without setting a language. I use a Lua filter to set the language of these code blocks to &amp;ldquo;text&amp;rdquo;. To run the filter, save it in a file, say &lt;code&gt;highlighting.lua&lt;/code&gt;, and invoke pandoc with &lt;code&gt;--lua-filter=highlighting.lua&lt;/code&gt;&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-file" data-callout-type="file"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-file"&gt;&lt;/i&gt;
&lt;code&gt;highlighting.lua&lt;/code&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-lua"&gt;function CodeBlock(el)
if #el.classes == 0 then
el.classes[1] = 'text'
end
return el
end
return {{CodeBlock = CodeBlock}}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h3 id="disable-figures-floating"&gt;Disable Figures Floating&lt;/h3&gt;
&lt;p&gt;The last issue to address is the floating of figures in LaTeX, which moves the images to avoid large blank space in pages. While LaTeX attempts to place figures in appropriate locations, authors may refer to them as &amp;ldquo;above&amp;rdquo; or &amp;ldquo;below&amp;rdquo; in the markdown files. This can be confusing for readers who may find that the referenced chart or diagram is actually on the following page in the PDF file.&lt;/p&gt;
&lt;p&gt;To solve the issue, simply disable floating figures. Create a file &lt;code&gt;disable-floating.tex&lt;/code&gt; and invoke pandoc with arguments &lt;code&gt;-H disable-floating.tex&lt;/code&gt;.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-file" data-callout-type="file"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-file"&gt;&lt;/i&gt;
&lt;code&gt;disable-floating.tex&lt;/code&gt;
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-latex"&gt;% https://stackoverflow.com/a/58840456/667158
\usepackage{float}
\let\origfigure\figure
\let\endorigfigure\endfigure
\renewenvironment{figure}[1][2] {
\expandafter\origfigure\expandafter[H]
} {
\endorigfigure
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id="putting-it-all-together"&gt;Putting It All Together&lt;/h2&gt;
&lt;p&gt;See the repo &lt;a href="https://github.com/doitian/pandoc-ide"&gt;doitian/pandoc-ide&lt;/a&gt;.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/latex/">LaTeX</category><category domain="https://blog.iany.me/tags/markdown/">Markdown</category><category domain="https://blog.iany.me/tags/pdf/">PDF</category></item><item><title>The Ultimate Guide to Customizing Obsidian Vim Mode via QuickAdd</title><link>https://blog.iany.me/2023/04/the-ultimate-guide-to-customizing-obsidian-vim-mode-via-quickadd/</link><pubDate>Thu, 27 Apr 2023 21:22:30 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2023/04/the-ultimate-guide-to-customizing-obsidian-vim-mode-via-quickadd/</guid><description>&lt;p&gt;Obsidian is my go-to note-taking app because of its availability on all desktop and mobile platforms with the bonus of Vim mode. In this guide, I&amp;rsquo;ll show you how I customize the Vim mode in Obsidian to maximize my note-taking efficiency.&lt;/p&gt;
&lt;h2 id="setup-user-launch-scripts-via-quickadd"&gt;Setup User Launch Scripts via QuickAdd&lt;/h2&gt;
&lt;p&gt;To customize the Vim mode in Obsidian, JavaScript is the only solution. Obsidian allows running user scripts by implementing an plugin. I use an easy alternative to run a JavaScript file on launch via QuickAdd.&lt;/p&gt;
&lt;p&gt;First, let’s create the JavaScript file in the vault. Save the skeleton below as the file &lt;code&gt;vimrc.js&lt;/code&gt; in any folder in the vault. Keep in mind that Obsidian does not support editing JavaScript files, so use an external editor.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-file" data-callout-type="file"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-file"&gt;&lt;/i&gt;
vimrc.js
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;function notice(text) {
new Notice(text);
return text;
}
module.exports = async function (context) {
const vim = window.CodeMirrorAdapter?.Vim;
if (vim === undefined) {
new Notice(`🔴error: vim mode is disabled`);
return;
}
// Add customizations here.
console.log(`🔵info: vimrc loaded`);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Then, install QuickAdd by browsing the community plugins in Settings and follow the instructions on the Obsidian help page &lt;a href="https://help.obsidian.md/Extending&amp;#43;Obsidian/Community&amp;#43;plugins"&gt;Community Plugins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, include the script in a QuickAdd macro and activate the option to execute the macro upon the plugin load.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the tab QuickAdd in Settings.&lt;/li&gt;
&lt;li&gt;Click the button “Manage Macros”. In the pop-up dialog, fill in the macro name “vimrc” and click the button “Add macro“ to add a new macro vimrc.&lt;/li&gt;
&lt;li&gt;Toggle the option “Run on plugin load” on under the newly created macro.&lt;/li&gt;
&lt;li&gt;Configure the macro vimrc. Find the script file &lt;code&gt;vimrc&lt;/code&gt; in the “User Scripts” section and add the script as the only step in the macro.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Attention that the script runs once on launch or reload. Running “Reload app without saving” via Command Palette will reload the file after changes,&lt;/p&gt;
&lt;h2 id="vim-mode-customization-techniques"&gt;Vim Mode Customization Techniques&lt;/h2&gt;
&lt;p&gt;For the best tutorial on customizing Vim mode and what methods the &lt;code&gt;vim&lt;/code&gt; object provides, refer to &lt;a href="https://github.com/replit/codemirror-vim/blob/master/src/vim.js"&gt;how CodeMirror establishes the Vim mode&lt;/a&gt;. I will only introduce the methods that I use in &lt;a href="https://github.com/doitian/quickadd-settings/blob/main/shore/quickadd/vimrc.js"&gt;my own customizations&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="vimdefineex"&gt;&lt;code&gt;vim.defineEx&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;defineEx&lt;/code&gt; method enables users to execute a new ex command in Vim by typing &lt;code&gt;:&lt;/code&gt; in normal mode. For instance, the ex command &lt;code&gt;:w&lt;/code&gt; saves the file.&lt;/p&gt;
&lt;p&gt;I introduce &lt;code&gt;defineEx&lt;/code&gt; first because I need an ex command to run any Obsidian command so that I can use it to add key bindings later. I name this command &lt;code&gt;obr&lt;/code&gt;, which is the abbreviation of &lt;strong&gt;OB&lt;/strong&gt;sidian &lt;strong&gt;R&lt;/strong&gt;un. The command requires the Obsidian command identifier as the argument, for example, &lt;code&gt;:obr app:open-settings&lt;/code&gt; will open the Settings dialog.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;vim.defineEx(&amp;quot;obr&amp;quot;, &amp;quot;&amp;quot;, function (cm, params) {
if (params?.args?.length !== 1) {
throw new Error(notice(&amp;quot;🔴error: obr requires exactly 1 parameter&amp;quot;));
}
const command = params.args[0];
context.app.commands.executeCommandById(command);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To find the command identifier, I add another ex command &lt;code&gt;:obl&lt;/code&gt; to display a list of identifiers and copy the selected one. It accepts an argument to filter the result. For example, &lt;code&gt;obl open&lt;/code&gt; will list identifiers that contain the word &amp;ldquo;open&amp;rdquo;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;vim.defineEx(&amp;quot;obl&amp;quot;, &amp;quot;&amp;quot;, async function (cm, params) {
let commands = Object.keys(context.app.commands.commands);
for (const keyword of params?.args ?? []) {
commands = commands.filter((command) =&amp;gt; command.includes(keyword));
}
const choice = await context.quickAddApi.suggester(commands, commands);
if (choice !== null) {
await context.quickAddApi.utility.setClipboard(choice);
new Notice(`🔵info: copied ${choice} to the clipboard`);
}
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="vimmap"&gt;&lt;code&gt;vim.map&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;vim.map&lt;/code&gt; method takes two arguments, &lt;code&gt;lhs&lt;/code&gt; and &lt;code&gt;rhs&lt;/code&gt;, which can both be either a key sequence or an ex command. The meaning of the method varies depending on the arguments passed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When both &lt;code&gt;lhs&lt;/code&gt; and &lt;code&gt;rhs&lt;/code&gt; are key sequence, it maps the key sequence &lt;code&gt;lhs&lt;/code&gt; to &lt;code&gt;rhs&lt;/code&gt;. For example, &lt;code&gt;vim.map('D', 'dd')&lt;/code&gt; will redefine &lt;code&gt;D&lt;/code&gt; to delete the whole line.&lt;/li&gt;
&lt;li&gt;When both &lt;code&gt;lhs&lt;/code&gt; and &lt;code&gt;rhs&lt;/code&gt; are ex commands, it creates a new ex command alias. For example, &lt;code&gt;vim.map(':Reload', ':obr app:reload')&lt;/code&gt; adds the alias &lt;code&gt;:Reload&lt;/code&gt; to run the Obsidian command &lt;code&gt;app:reload&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;lhs&lt;/code&gt; is a key sequence, and &lt;code&gt;rhs&lt;/code&gt; is an ex command, it maps the key sequence to run the ex command. For example &lt;code&gt;vim.map('ZZ', ':obr app:reload')&lt;/code&gt; allows reloading Obsidian by typing ZZ.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;lhs&lt;/code&gt; is an ex command, and &lt;code&gt;rhs&lt;/code&gt; is a key sequence, executing the ex command has the same effect as typing the key sequence.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have a bunch of mappings from key sequences to &lt;code&gt;:obr&lt;/code&gt;, for example, the z-family to operate on folds:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;vim.map(&amp;quot;zo&amp;quot;, &amp;quot;:obr editor:toggle-fold&amp;quot;);
vim.map(&amp;quot;zc&amp;quot;, &amp;quot;:obr editor:toggle-fold&amp;quot;);
vim.map(&amp;quot;za&amp;quot;, &amp;quot;:obr editor:toggle-fold&amp;quot;);
vim.map(&amp;quot;zR&amp;quot;, &amp;quot;:obr editor:unfold-all&amp;quot;);
vim.map(&amp;quot;zM&amp;quot;, &amp;quot;:obr editor:fold-all&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use the space key as a key mapping prefix, it must first be &lt;code&gt;unmap&lt;/code&gt;ped.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;vim.unmap(&amp;quot;&amp;lt;Space&amp;gt;&amp;quot;);
vim.map(&amp;quot;&amp;lt;Space&amp;gt;&amp;lt;Space&amp;gt;&amp;quot;, &amp;quot;:obr switcher:open&amp;quot;);
vim.map(&amp;quot;&amp;lt;Space&amp;gt;n&amp;quot;, &amp;quot;:nohl&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="vimdefineaction-and--vimdefineoperator"&gt;&lt;code&gt;vim.defineAction&lt;/code&gt; and &lt;code&gt;vim.defineOperator&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;We can use both action and operator in &lt;code&gt;vim.mapCommand&lt;/code&gt;. The difference is that an operator performs upon text objects. See how CodeMirror defines the built-in &lt;a href="https://github.com/replit/codemirror-vim/blob/7e70ff7d321f9aa6600616a4d2ee81327394533a/src/vim.js#L2450"&gt;actions&lt;/a&gt; and &lt;a href="https://github.com/replit/codemirror-vim/blob/7e70ff7d321f9aa6600616a4d2ee81327394533a/src/vim.js#L2290"&gt;operators&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have an action &lt;code&gt;swapLine&lt;/code&gt; to move lines around. It&amp;rsquo;s not perfect, since undo will reverse one step only.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;vim.defineAction(&amp;quot;swapLine&amp;quot;, function(_cm, { repeat, down }) {
const command = down ? &amp;quot;editor:swap-line-down&amp;quot; : &amp;quot;editor:swap-line-up&amp;quot;;
for (let i = 0; i &amp;lt; repeat; i++) {
context.app.commands.executeCommandById(command);
}
});
// undo after 5]e will only swap one line up.
vim.mapCommand(&amp;quot;]e&amp;quot;, &amp;quot;action&amp;quot;, &amp;quot;swapLine&amp;quot;, { down: true });
vim.mapCommand(&amp;quot;[e&amp;quot;, &amp;quot;action&amp;quot;, &amp;quot;swapLine&amp;quot;, { down: false });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example for &lt;code&gt;vim.defineOperator&lt;/code&gt; is &lt;code&gt;titleCase&lt;/code&gt;, which formats the text in the &lt;a href="https://kb.iany.me/para/lets/w/Writing/Format&amp;#43;the&amp;#43;Title&amp;#43;Using&amp;#43;APA&amp;#43;Title&amp;#43;Case&amp;#43;Capitalization"&gt;APA Title Case Capitalization&lt;/a&gt;. For instance, &lt;code&gt;gzap&lt;/code&gt; formats the current paragraph in the title case.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;function titleCase(str, options) {
// See https://github.com/words/ap-style-title-case/blob/master/index.js how to format str to apa title case
}
// Following helper funnctions are borrowed from https://codemirror.net/5/keymap/vim.js
function cursorIsBefore(cur1, cur2) {
if (cur1.line &amp;lt; cur2.line) {
return true;
}
if (cur1.line == cur2.line &amp;amp;&amp;amp; cur1.ch &amp;lt; cur2.ch) {
return true;
}
return false;
}
function cursorMin(cur1, cur2) {
return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
}
function findFirstNonWhiteSpaceCharacter(text) {
if (!text) {
return 0;
}
var firstNonWS = text.search(/\S/);
return firstNonWS == -1 ? text.length : firstNonWS;
}
vim.defineOperator(&amp;quot;titleCase&amp;quot;, function(cm, args, ranges, oldAnchor, newHead) {
const selections = cm.getSelections();
const newSelections = selections.map((s) =&amp;gt;
titleCase(s, { keepSpaces: true })
);
cm.replaceSelections(newSelections);
if (args.shouldMoveCursor) {
return newHead;
} else if (
!cm.state.vim.visualMode &amp;amp;&amp;amp;
args.linewise &amp;amp;&amp;amp;
ranges[0].anchor.line + 1 == ranges[0].head.line
) {
return {
line: oldAnchor.line,
ch: findFirstNonWhiteSpaceCharacter(cm.getLine(oldAnchor.line)),
};
} else if (args.linewise) {
return oldAnchor;
} else {
return cursorMin(ranges[0].anchor, ranges[0].head);
}
return newHead;
});
vim.mapCommand(&amp;quot;gz&amp;quot;, &amp;quot;operator&amp;quot;, &amp;quot;titleCase&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/obsidian/">Obsidian</category><category domain="https://blog.iany.me/tags/vim/">Vim</category></item><item><title>Practice Autofocus Method in Todoist</title><link>https://blog.iany.me/2022/10/practice-autofocus-method-in-todoist/</link><pubDate>Mon, 03 Oct 2022 19:26:38 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2022/10/practice-autofocus-method-in-todoist/</guid><description>&lt;p&gt;I read the article &lt;em&gt;Autofocus: The Productivity System That Treats Your to-Do List Like a River&lt;/em&gt;&lt;sup id="fnxref:1"&gt;&lt;a href="https://blog.iany.me/2022/10/practice-autofocus-method-in-todoist/#fnx:1"&gt;1&lt;/a&gt;&lt;/sup&gt; recently. I used to fill the time slots with tasks daily, but I rarely completed all the planned tasks. Even for the completed ones, I often missed the scheduled time slot. I felt stressed and guilty. So I decide to give the Autofocus Method a try.&lt;/p&gt;
&lt;h2 id="the-river-filter"&gt;The River Filter&lt;/h2&gt;
&lt;p&gt;I will continue using Todoist. I created a filter named &lt;em&gt;River&lt;/em&gt;. The tasks are sorted by added date. I added the filter to the sidebar and set it as my home view for easy access.&lt;/p&gt;
&lt;p&gt;The filter has three sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Overdue tasks: &lt;code&gt;overdue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tasks I am doing: &lt;code&gt;today | @now💧&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tasks in River: &lt;code&gt;!##⏰ Events &amp;amp; !overdue &amp;amp; !today &amp;amp; !@now💧 &amp;amp; !@someday🪨&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Todoist supports filter sections by separating queries with comma (&lt;code&gt;,&lt;/code&gt;). So here is the final filter query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;overdue, today | @now💧, !##⏰ Events &amp;amp; !overdue &amp;amp; !today &amp;amp; !@now💧 &amp;amp; !@someday🪨
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="section-1-overdue-tasks"&gt;Section 1: Overdue Tasks&lt;/h3&gt;
&lt;p&gt;This section allows me to re-schedule the overdue tasks.&lt;/p&gt;
&lt;p&gt;I decided to not totally abandon the due dates. However, I use them with great moderation. I set a due date if I must complete the task on that date, and before the day comes, I can forget the task.&lt;/p&gt;
&lt;p&gt;I add such tasks to the project &lt;code&gt;⏰ Events&lt;/code&gt;. They will appear in the River filter when they are due or overdue.&lt;/p&gt;
&lt;h3 id="section-2-tasks-i-am-doing"&gt;Section 2: Tasks I Am Doing&lt;/h3&gt;
&lt;p&gt;This section lists tasks that I am doing now.&lt;/p&gt;
&lt;p&gt;There are two kinds of such tasks.&lt;/p&gt;
&lt;p&gt;The first is the scheduled tasks that are due today.&lt;/p&gt;
&lt;p&gt;The second is the selected tasks from the river. I add the tag &lt;code&gt;@now💧&lt;/code&gt; to the task if I feel like doing it.&lt;/p&gt;
&lt;h3 id="section-3-tasks-in-river"&gt;Section 3: Tasks in River&lt;/h3&gt;
&lt;p&gt;This section contains the tasks in the river. It excludes tasks from the project &lt;code&gt;⏰ Events&lt;/code&gt;, and the dismissed tasks. I tag dismissed tasks with &lt;code&gt;@someday🪨&lt;/code&gt; .&lt;/p&gt;
&lt;h2 id="the-workflow"&gt;The Workflow&lt;/h2&gt;
&lt;p&gt;I add new tasks to the inbox without triaging the projects, tags, due dates, and priorities. Since the filter&lt;/p&gt;
&lt;p&gt;When I have time to do something, I open the River filter and scan the tasks from top to bottom without scrolling down.&lt;/p&gt;
&lt;p&gt;First, I re-schedule the overdue tasks.&lt;/p&gt;
&lt;p&gt;Then, I do a quick scan on the second section—the tasks that I planned before. If I feel like doing a task in the list, I start working on it.&lt;/p&gt;
&lt;p&gt;If I failed to find a task to work on in the previous step, I continue the scan to the last section—the tasks in the river. I add tag &lt;code&gt;@now💧&lt;/code&gt; to pop the task to the top of the list.&lt;/p&gt;
&lt;p&gt;If there are no items stands out for me, I have three choices to remove tasks from the first screen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Defer the scheduled tasks into future.&lt;/li&gt;
&lt;li&gt;Remove the tag &lt;code&gt;@now💧&lt;/code&gt; from the task.&lt;/li&gt;
&lt;li&gt;Dismiss a task by adding the tag &lt;code&gt;@someday🪨&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Autofocus Method suggests to cross the item off the list and re-enter it at the end of the list if I haven&amp;rsquo;t finished it yet. In Todoist, I duplicate the task and cross the original item off.&lt;/p&gt;
&lt;p&gt;Here is my setup to practice the Autofocus Method in Todoist. I&amp;rsquo;m still experimenting to see whether it can improve my productivity.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a name="fnx:1"&gt;&lt;/a&gt; McKay, B., &amp;amp; McKay, K. (2022, September 20). &lt;em&gt;Autofocus: The Productivity System That Treats Your To-Do List Like a River&lt;/em&gt;. The Art of Manliness. &lt;a href="https://www.artofmanliness.com/character/behavior/autofocus-the-productivity-system-that-treats-your-to-do-list-like-a-river/"&gt;https://www.artofmanliness.com/character/behavior/autofocus-the-productivity-system-that-treats-your-to-do-list-like-a-river/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category><category domain="https://blog.iany.me/tags/todoist/">Todoist</category></item><item><title>Set the Minimum Width of the Active Pane in Obsidian</title><link>https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/</link><pubDate>Fri, 23 Sep 2022 16:44:51 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/</guid><description>&lt;details open disabled class="kg-card kg-callout kg-callout-info" data-callout-type="info"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-circle-info"&gt;&lt;/i&gt;
Updates
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
I have created &lt;a href="https://github.com/doitian/obsidian-min-width"&gt;an plugin&lt;/a&gt; for this feature.
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;A simple CSS rule sets the minimum width of the active pane in Obsidian.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-file" data-callout-type="file"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-file"&gt;&lt;/i&gt;
min-width.css
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;.mod-root .mod-active {
min-width: min(88%, 40rem);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;It sets the minimum width to either 40 columns or 88% of the whole editing area, depending on which is smaller. Obsidian will auto resize the active pane to ensure it is wider enough and shrink other panes accordingly.&lt;/p&gt;
&lt;p&gt;The snippet does not work in horizontal splits where panes stack vertically. See the chapter &lt;a href="https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/#javascript-helper"&gt;JavaScript Helper&lt;/a&gt; below how to work around it.&lt;/p&gt;
&lt;h2 id="how-to-add-css-snippets-in-obsidian"&gt;How to Add CSS Snippets in Obsidian&lt;/h2&gt;
&lt;p&gt;Save the CSS snippet as file &lt;code&gt;.obsidian/snippets/min-width.css&lt;/code&gt; in the vault. Create the folder &lt;code&gt;.obsidian/snippets&lt;/code&gt; if it does not exist.&lt;/p&gt;
&lt;p&gt;Now the file should appear in the Appearance settings page. Toggle the switcher to enable the CSS snippet in the file.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Enable CSS Snippet in Obsidian" class="kg-image" loading="lazy" src="https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/enable-obsidian-min-width-css-snippet_hu_3cfbb421daa9a8a0.png" srcset="https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/enable-obsidian-min-width-css-snippet_hu_49c9540d03943c.png 400w, https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/enable-obsidian-min-width-css-snippet_hu_310cf0db5ebd947c.png 800w, https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/enable-obsidian-min-width-css-snippet_hu_42073e0e4bace7f5.png 1200w, https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/enable-obsidian-min-width-css-snippet_hu_3cfbb421daa9a8a0.png 1524w" sizes="(max-width: 1200px) 100vw, 1524px" /&gt;
&lt;figcaption &gt;Enable CSS Snippet in Obsidian&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="javascript-helper"&gt;JavaScript Helper&lt;/h2&gt;
&lt;p&gt;CSS is useless when it can not select the target elements. To make the snippet work in horizontal splits, the CSS rule has to select the &lt;code&gt;.mod-horizontal&lt;/code&gt; node when it has a &lt;code&gt;.mod-active&lt;/code&gt; child. However, the pseudo-class selector &lt;code&gt;:has()&lt;/code&gt;&lt;sup id="fnref:1"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;1&lt;/span&gt;&lt;/sup&gt; is not available in Obsidian yet.&lt;/p&gt;
&lt;p&gt;JavaScript can help to add classes and attributes to HTML elements.&lt;/p&gt;
&lt;p&gt;I also want to make some panes wider, such as Excalibrain&lt;sup id="fnref:2"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;2&lt;/span&gt;&lt;/sup&gt;. The JavaScript code can also set the view type to the proper elements. The view type of the Excalibrain pane is &lt;code&gt;excalidraw&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, update the CSS snippet to make Excalibrain panes wider. Indeed, it makes all Excalidraw panes wider.&lt;/p&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-file" data-callout-type="file"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-file"&gt;&lt;/i&gt;
min-width.css
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;.mod-root .mod-active {
min-width: min(88%, 40rem);
}
.mod-root .mod-active[data-type=&amp;quot;excalidraw&amp;quot;] {
min-width: min(88%, 60rem);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;The last, let Obsidian run some code when the active pane has changed. I use QuickAdd&lt;sup id="fnref:3"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;3&lt;/span&gt;&lt;/sup&gt; to manage the scripts. Save the following code snippet as &lt;code&gt;min-width.js&lt;/code&gt; in the vault.&lt;/p&gt;
&lt;details class="kg-card kg-callout kg-callout-file" data-callout-type="file"&gt;
&lt;summary class="kg-callout-title"&gt;
&lt;i class="fas fa-chevron-right"&gt;&lt;/i&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-file"&gt;&lt;/i&gt;
min-width.js
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;const DATA_TYPE = &amp;quot;data-type&amp;quot;;
function setOrRemoveDataType(el, dataType) {
if (dataType !== undefined) {
el.setAttribute(DATA_TYPE, dataType);
} else {
el.removeAttribute(DATA_TYPE);
}
}
module.exports = async function ({ app }) {
app.workspace.on(&amp;quot;active-leaf-change&amp;quot;, (leaf) =&amp;gt; {
// clear .mod-active on .mod-horizontal
for (const el of app.workspace.containerEl.getElementsByClassName(
&amp;quot;mod-active mod-horizontal&amp;quot;
)) {
el.classList.remove(&amp;quot;mod-active&amp;quot;);
}
// bubble up data-type
const dataType = leaf.containerEl
.getElementsByClassName(&amp;quot;workspace-leaf-content&amp;quot;)[0]
?.getAttribute(DATA_TYPE);
setOrRemoveDataType(leaf.containerEl, dataType);
// add .mod-active and data-type to current horizontal split container
const parentNode = leaf.containerEl.parentNode;
if (parentNode.classList.contains(&amp;quot;mod-horizontal&amp;quot;)) {
parentNode.classList.add(&amp;quot;mod-active&amp;quot;);
setOrRemoveDataType(parentNode, dataType);
}
});
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Then run it when Obsidian starts by following these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the button &amp;ldquo;Manage Macros&amp;rdquo; in the QuickAdd settings.&lt;/li&gt;
&lt;li&gt;Add a new macro and add the user script &amp;ldquo;min-width&amp;rdquo; to the macro.&lt;/li&gt;
&lt;li&gt;Save the macro and check the option &amp;ldquo;Run on plugin load&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="kg-card kg-gallery-card kg-width-wide"&gt;
&lt;div class="kg-gallery-container"&gt;
&lt;div class="kg-gallery-row"&gt;
&lt;div class="kg-gallery-image"&gt;&lt;img alt="Add a Macro" class="tippy" loading="lazy" width="1392" height="976" src="https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/add-obsidian-min-width-macro_hu_44c5d03588510340.png" srcset="https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/add-obsidian-min-width-macro_hu_5994f64990ff5e57.png 400w, https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/add-obsidian-min-width-macro_hu_250ee3ab71bd8cd6.png 800w, https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/add-obsidian-min-width-macro_hu_76b98ea621b18686.png 1200w, https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/add-obsidian-min-width-macro_hu_44c5d03588510340.png 1392w" sizes="(max-width: 1200px) 100vw, 1392px" data-tippy-content="Add a Macro" /&gt;&lt;/div&gt;
&lt;div class="kg-gallery-image"&gt;&lt;img alt="Run on plugin load" class="tippy" loading="lazy" width="434" height="685" src="https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/run-min-width-script-on-plugin-load_hu_126afbe006f14a74.png" srcset="https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/run-min-width-script-on-plugin-load_hu_fa193c4e1eff2fa7.png 400w, https://blog.iany.me/2022/09/set-the-minimum-width-of-the-active-pane-in-obsidian/run-min-width-script-on-plugin-load_hu_126afbe006f14a74.png 434w" sizes="(max-width: 400px) 100vw, 434px" data-tippy-content="Run on plugin load" /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figcaption&gt;Run JavaScript Code on Start With QuickAdd&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;That&amp;rsquo;s it. Restart Obsidian and the code should be effective. See how it works in the screen recording below.&lt;/p&gt;
&lt;figure class="kg-card kg-embed-card"&gt;
&lt;iframe src="https://player.vimeo.com/video/752964835" width="640" height="391" frameborder="0" allow="autoplay; fullscreen" allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;Set the Minimum Width of the Active Pane in Obsidian&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There are two extra plugins used in the video, Snippet Commands&lt;sup id="fnref:4"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;4&lt;/span&gt;&lt;/sup&gt; and Cycle through Panes.&lt;sup id="fnref:5"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;5&lt;/span&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id="similar-work"&gt;Similar Work&lt;/h2&gt;
&lt;p&gt;This work is inspired by the Vim option &lt;code&gt;winwidth&lt;/code&gt;, one of my favorite feature of Vim.&lt;/p&gt;
&lt;p&gt;The awesome plugin Sliding Panes&lt;sup id="fnref:6"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;6&lt;/span&gt;&lt;/sup&gt; provides the similar feature for Obsidian. However, it has three problems that I cannot stand.&lt;/p&gt;
&lt;p&gt;The first, when the option &amp;ldquo;Leaf Width on Desktop/Mobile&amp;rdquo; is wider than the whole editing area, I have to scroll horizontally to see the current active pane. Although the plugin has separated the options for Desktop and Mobile, but different devices have different screen sizes. What&amp;rsquo;s worser, the app window can be any size on Desktop. The option must be less than the smallest screen width when Obsidian Sync is in use, which is not a good choice for wider devices.&lt;/p&gt;
&lt;p&gt;The second, the plugin does not play well with horizontal splits.&lt;/p&gt;
&lt;p&gt;The last, I really want to set the different minimum widths for different windows.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;MDN. (2016, October 27). :&lt;em&gt;has() - CSS: Cascading Style Sheets&lt;/em&gt;. MDN. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has"&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/:has&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;zsviczian. (2022, May). &lt;em&gt;Excalibrain: A Graph View to Navigate Your Obsidian Vault&lt;/em&gt;. GitHub. &lt;a href="https://github.com/zsviczian/excalibrain"&gt;https://github.com/zsviczian/excalibrain&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;chhoumann. (2021, June). &lt;em&gt;QuickAdd for Obsidian&lt;/em&gt;. GitHub. &lt;a href="https://github.com/chhoumann/quickadd"&gt;https://github.com/chhoumann/quickadd&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;deathau. (2021, October). &lt;em&gt;Snippet Commands Obsidian Plugin&lt;/em&gt;. GitHub. &lt;a href="https://github.com/deathau/snippet-commands-obsidian"&gt;https://github.com/deathau/snippet-commands-obsidian&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;phibr0. (2020, November). &lt;em&gt;Cycle Through Panes Obsidian Plugin&lt;/em&gt;. GitHub. &lt;a href="https://github.com/phibr0/cycle-through-panes"&gt;https://github.com/phibr0/cycle-through-panes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;deathau. (2020, October). &lt;em&gt;Sliding Panes (Andy Matuschak Mode) Obsidian Plugin&lt;/em&gt;. GitHub. &lt;a href="https://github.com/deathau/sliding-panes-obsidian"&gt;https://github.com/deathau/sliding-panes-obsidian&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/css/">CSS</category><category domain="https://blog.iany.me/tags/obsidian/">Obsidian</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category></item><item><title>Use Italic Font for Coding</title><link>https://blog.iany.me/2021/09/use-italic-font-for-coding/</link><pubDate>Sat, 11 Sep 2021 21:21:30 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2021/09/use-italic-font-for-coding/</guid><description>&lt;p&gt;I discovered &lt;a href="https://marketplace.visualstudio.com/items?itemName=liviuschera.noctis"&gt;Noctis&lt;/a&gt; theme recently and found that the font &lt;a href="https://connary.com/cartograph.html"&gt;Cartograph CF&lt;/a&gt; is really interesting.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cartograph is a handsome monospaced font family featuring lush italics, code-friendly ligatures&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s funny to see those handwriting like italic characters while coding, so I purchased the font and integrated it into my tools.&lt;/p&gt;
&lt;p&gt;For Visual Studio Code, I use Noctis. But it uses too much italic style, which decreases the readability. Here are my customizations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
&amp;quot;editor.tokenColorCustomizations&amp;quot;: {
&amp;quot;[Noctis]&amp;quot;: {
&amp;quot;textMateRules&amp;quot;: [
{
&amp;quot;scope&amp;quot;: &amp;quot;markup.list&amp;quot;,
&amp;quot;settings&amp;quot;: { &amp;quot;fontStyle&amp;quot;: &amp;quot;&amp;quot; }
},
{
&amp;quot;scope&amp;quot;: &amp;quot;text.markdown.notes.tag&amp;quot;,
&amp;quot;settings&amp;quot;: { &amp;quot;fontStyle&amp;quot;: &amp;quot;italic&amp;quot; }
}
]
}
},
// ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="kg-image-card kg-width-wide"&gt;
&lt;img alt="Visual Studio Code" class="kg-image" loading="lazy" src="https://blog.iany.me/2021/09/use-italic-font-for-coding/vscode_hu_86a0e1dbb8c512ce.png" srcset="https://blog.iany.me/2021/09/use-italic-font-for-coding/vscode_hu_27c1cff861c6d229.png 400w, https://blog.iany.me/2021/09/use-italic-font-for-coding/vscode_hu_73cbb4cd89465103.png 800w, https://blog.iany.me/2021/09/use-italic-font-for-coding/vscode_hu_e7334215202d837a.png 1200w, https://blog.iany.me/2021/09/use-italic-font-for-coding/vscode_hu_434a0619d3bf9264.png 1600w, https://blog.iany.me/2021/09/use-italic-font-for-coding/vscode_hu_83b367269ae14d38.png 2000w, https://blog.iany.me/2021/09/use-italic-font-for-coding/vscode_hu_86a0e1dbb8c512ce.png 2838w" sizes="(max-width: 2000px) 100vw, 2838px" /&gt;
&lt;figcaption &gt;Visual Studio Code&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It&amp;rsquo;s handy to inspect the syntax highlight scope under cursor using the command &amp;ldquo;Developer: Inspect Editor Tokens and Scopes&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;In macOS Terminal, I can use the escape code &lt;code&gt;\e[3m&lt;/code&gt; to enable italic and &lt;code&gt;\e[23m&lt;/code&gt; to reset.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;printf &amp;quot;\e[3mitalic\e[23m&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or using echo&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;echo &amp;quot;^[[3mitalic^[[23m&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where the &lt;code&gt;^[&lt;/code&gt; is indeed how the terminal displays Esc. One way to insert Esc is via &lt;code&gt;Ctrl-v Esc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I switched to &lt;a href="https://starship.rs/"&gt;starship&lt;/a&gt; recently, so I don&amp;rsquo;t need to bother setting up the italic fonts in my shell prompt by myself. Here is my starship config file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-toml"&gt;format = &amp;quot;[$all](italic)&amp;quot;
[directory]
format = '[%\(4~|%-1~/…/%2~|%~\)]($style)[$read_only]($read_only_style) '
[git_branch]
symbol = &amp;quot;±&amp;quot;
style = &amp;quot;purple bold italic&amp;quot;
[status]
disabled = false
[nodejs]
symbol = '🤖 '
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I changed the nodejs symbol, because Cartograph CF has no the nerd font icons.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a heavy user of tmux, it&amp;rsquo;s tricky to enable italics in tmux. The secret is setting &lt;code&gt;default-terminal&lt;/code&gt; to tmux.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;set -g default-terminal &amp;quot;tmux&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then set back the terminal to &lt;code&gt;xterm-256color&lt;/code&gt; via &lt;code&gt;default-command&lt;/code&gt; because macOS Terminal does not understand what is the &lt;em&gt;tmux&lt;/em&gt; terminal.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;set -g default-command &amp;quot;env TERM=xterm-256color zsh&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I only configure the session name to be italic.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;set -g status-left &amp;quot;#[fg=white,bg=colour31,nobold,italics] #S &amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I cannot find a cross-platform way to show italic font in vim, so I manually enable it when I want to see the funny italic characters in the comment:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-vim"&gt;function! s:Italic(enable)
if a:enable
hi Comment cterm=italic gui=italic
hi Folded cterm=italic gui=italic
let &amp;amp;t_ZH = &amp;quot;\e[3m&amp;quot;
let &amp;amp;t_ZR = &amp;quot;\e[23m&amp;quot;
else
hi Comment cterm=none gui=none
hi Folded cterm=none gui=none
let &amp;amp;t_ZH = ''
let &amp;amp;t_ZR = ''
endif
endfunction
command! ItalicEnable call s:Italic(1)
command! ItalicDisable call s:Italic(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="kg-image-card kg-width-wide"&gt;
&lt;img alt="Shell, Tmux and Vim" class="kg-image" loading="lazy" src="https://blog.iany.me/2021/09/use-italic-font-for-coding/shell-tmux-vim_hu_10dda27268ca9775.png" srcset="https://blog.iany.me/2021/09/use-italic-font-for-coding/shell-tmux-vim_hu_c33f44053e0ac874.png 400w, https://blog.iany.me/2021/09/use-italic-font-for-coding/shell-tmux-vim_hu_7ee0fdf1e595205f.png 800w, https://blog.iany.me/2021/09/use-italic-font-for-coding/shell-tmux-vim_hu_6eb0456fb69ffcde.png 1200w, https://blog.iany.me/2021/09/use-italic-font-for-coding/shell-tmux-vim_hu_3cbeb0692be9fcb4.png 1600w, https://blog.iany.me/2021/09/use-italic-font-for-coding/shell-tmux-vim_hu_e4a941b78d9e06ca.png 2000w, https://blog.iany.me/2021/09/use-italic-font-for-coding/shell-tmux-vim_hu_10dda27268ca9775.png 2454w" sizes="(max-width: 2000px) 100vw, 2454px" /&gt;
&lt;figcaption &gt;Shell, Tmux and Vim&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The last tool is &lt;a href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt;, I use the CSS snippet to customize the appearance:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;:root {
--font-monospace: 'Cartograph CF', monospace;
}
.cm-meta, .cm-hmd-frontmatter, .tag, .image-embed[alt]:after {
font-style: italic;
font-family: var(--font-monospace);
}
.cm-meta.cm-formatting-task {
font-style: normal;
font-family: var(--default-font);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fortunately, it also works on iOS after installing the fonts to the system via &lt;a href="https://apps.apple.com/us/app/fontcase-manage-your-type/id1205074470"&gt;Fontcase&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have also tested several frequently used iOS apps.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.textasticapp.com/"&gt;Textastic&lt;/a&gt; can use the fonts installed by Fontcase, and Cartograph CF works perfect. The app &lt;a href="https://holzschu.github.io/a-Shell_iOS/"&gt;a-Shell&lt;/a&gt; is also fine to use the self installed fonts.&lt;/p&gt;
&lt;p&gt;But &lt;a href="https://blink.sh/"&gt;Blink&lt;/a&gt; does not support system fonts. Although it can install fonts via CSS, I cannot make Blink display Cartograph CF using the correct line height. It also makes Blink sluggish and unstable.&lt;/p&gt;
&lt;p&gt;I use an Android phone currently, but I give up the joy to use Cartograph CF on my phone. Because I don&amp;rsquo;t use it for work and it seems hard to install fonts on Android. I tried to make it work in Obsidian, by copying fonts to Obsidian vault and using remote font URL, but both failed.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/console/">Console</category><category domain="https://blog.iany.me/tags/obsidian/">Obsidian</category><category domain="https://blog.iany.me/tags/tmux/">Tmux</category><category domain="https://blog.iany.me/tags/typography/">Typography</category><category domain="https://blog.iany.me/tags/vim/">Vim</category></item><item><title>SSH Authentication Using a YubiKey on Windows And the OpenSSH Client</title><link>https://blog.iany.me/2021/02/ssh-authentication-using-a-yubikey-on-windows-and-the-openssh-client/</link><pubDate>Tue, 09 Feb 2021 20:08:23 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2021/02/ssh-authentication-using-a-yubikey-on-windows-and-the-openssh-client/</guid><description>&lt;p&gt;As mentioned in &lt;a href="https://blog.iany.me/2020/07/yubico-for-windows/"&gt;♯ Yubico for Windows&lt;/a&gt;, I used PuTTY/Plink instead of the OpenSSH client together with YubiKey because the OpenSSH Client does not support the socket file created by GnuPG.&lt;/p&gt;
&lt;p&gt;Plink does not work well in Windows Terminal. The Visual Studio Code &lt;a href="https://code.visualstudio.com/docs/remote/ssh-tutorial"&gt;SSH Remote&lt;/a&gt; does not support Plink as well, because it will pass some command line arguments that are not supported by Plink.&lt;/p&gt;
&lt;p&gt;So I decide to switch back to the OpenSSH client. Fortunately, the utility &lt;a href="https://github.com/benpye/wsl-ssh-pageant"&gt;wsl-ssh-pageant&lt;/a&gt; can create a tunnel between a Windows pipe and the pageant socket, and the OpenSSH client can use the Windows pipe as &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt;. This article is a tutorial to set up wsl-ssh-pageant.&lt;/p&gt;
&lt;h2 id="enable-putty-support"&gt;Enable PuTTY Support&lt;/h2&gt;
&lt;p&gt;First edit &lt;code&gt;gpg-agent.conf&lt;/code&gt; in folder &lt;code&gt;$(scoop prefix gpg)\home\&lt;/code&gt;, enable PuTTY support&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;enable-putty-support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart gpg agent to reload the config file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg-connect-agent killagent /bye
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="install-openssh-client"&gt;Install OpenSSH Client&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;This section is copied from &lt;a href="https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse"&gt;Installation of OpenSSH For Windows Server | Microsoft Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To install OpenSSH, start Settings then go to Apps &amp;gt; Apps and Features &amp;gt; Manage Optional Features. Or install it using PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WindowsCapability -Online | ? Name -like 'OpenSSH*'
# This should return the following output:
Name : OpenSSH.Client~~~~0.0.1.0
State : NotPresent
Name : OpenSSH.Server~~~~0.0.1.0
State : NotPresent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, install the client feature:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Install the OpenSSH Client
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
# It should return the following output:
Path :
Online : True
RestartNeeded : False
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="set-up-wsl-ssh-pageant"&gt;Set Up wsl-ssh-pageant&lt;/h2&gt;
&lt;p&gt;Install &lt;a href="https://github.com/benpye/wsl-ssh-pageant"&gt;wsl-ssh-pageant&lt;/a&gt; by downloading the binary from the GitHub release or using scoop&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scoop install wsl-ssh-pageant
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a cmd file &lt;code&gt;winssh-agent.cmd&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-batch"&gt;wsl-ssh-pageant.exe --systray --winssh ssh-pageant
PAUSE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Double-clicking this file will start the tunnel.&lt;/p&gt;
&lt;p&gt;Add the environment variable to tell OpenSSH client to use the pipe created by &lt;code&gt;wsl-ssh-pageant&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;[Environment]::SetEnvironmentVariable('SSH_AUTH_SOCK', '\\.\pipe\ssh-pageant', 'User')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The environment variable is only effective in new windows, so start a new terminal window to try that the tunnel works.&lt;/p&gt;
&lt;p&gt;First, start the gpg agent and check that the card reader works:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --card-status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now try to authenticate SSH, for example, to GitHub&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh git@github.com
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="auto-start-wsl-ssh-pageant"&gt;Auto Start wsl-ssh-pageant&lt;/h2&gt;
&lt;p&gt;Create a shortcut in Windows Start Menu to auto-start wsl-ssh-pageant on login and allow launch it by searching in the start menu by pressing the Windows key.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-powershell"&gt;# Change the path to the cmd file.
$SSHAgentLocation = &amp;quot;X:\Path\to\winssh-agent.cmd&amp;quot;
$SSHAgentShortcut = &amp;quot;$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\SSH Agent.lnk&amp;quot;
$WScriptShell = New-Object -ComObject WScript.Shell
$Shortcut = $WScriptShell.CreateShortcut($SSHAgentShortcut)
$Shortcut.TargetPath = $SSHAgentLocation
$Shortcut.WindowStyle = 7
$Shortcut.Save()
mkdir -Fo &amp;quot;$env:APPDATA\Microsoft\Windows\Start Menu\Programs\SSH Agent&amp;quot;
cp -Fo &amp;quot;$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\SSH Agent.lnk&amp;quot; &amp;quot;$env:APPDATA\Microsoft\Windows\Start Menu\Programs\SSH Agent\SSH Agent.lnk&amp;quot;
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/ssh/">SSH</category><category domain="https://blog.iany.me/tags/windows/">Windows</category><category domain="https://blog.iany.me/tags/yubikey/">YubiKey</category></item><item><title>Install macOS Big Sur in 2021</title><link>https://blog.iany.me/2021/02/install-macos-big-sur-in-2021/</link><pubDate>Sat, 06 Feb 2021 20:06:11 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2021/02/install-macos-big-sur-in-2021/</guid><description>&lt;p&gt;My MacBook&amp;rsquo;s battery capacity is significantly reduced and it has many weird issues so I decide to do a clean installation of macOS Big Sur. This article is a reference for my own reference in case I need to do it again.&lt;/p&gt;
&lt;h2 id="preparation"&gt;Preparation&lt;/h2&gt;
&lt;p&gt;I plan to erase the disk and install a clean copy of macOS, so the first step is creating the bootable installer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Back up the data. Follow &lt;a href="https://support.apple.com/en-us/HT201065"&gt;these instructions&lt;/a&gt; if I want to sell, give away, or trade in my Mac.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get Big Sur from the App Store. This will install the app &lt;code&gt;Install macOS Big Sur.app&lt;/code&gt; into &lt;code&gt;/Applications&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Insert the USB stick which has at least 8GB capacity. Assuming the stick name is &lt;code&gt;MyVolume&lt;/code&gt;, create the installer using the USB stick with following command &lt;sup id="fnref:1"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;1&lt;/span&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo /Applications/Install\ macOS\ Big\ Sur.app/Contents/Resources/createinstallmedia --downloadassets --volume /Volumes/MyVolume
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The option &lt;code&gt;--downloadassets&lt;/code&gt; is optional but is recommended, because without proxy, it is slow to download resources during the installating stage in some regions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reboot the laptop into recovery mode by holding &lt;kbd&gt;Command+R&lt;/kbd&gt;. Choose Utilities &amp;gt; Startup Security Utility from the menu bar and allow booting from external media.&lt;sup id="fnref:2"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;2&lt;/span&gt;&lt;/sup&gt; This step is &lt;strong&gt;very important&lt;/strong&gt; and must be done before erasing the disk because it requires verifying the admin user password in the installed system. I fogot it and had to recover the factory macOS version to just create a user so that I can allow booting from external media.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure there&amp;rsquo;s a working Internet connection. The installation requires network to do verification.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="install"&gt;Install&lt;/h2&gt;
&lt;p&gt;Keep the USB stick inserted. Reboot the laptop by holding Option (⌥) this time. This will bring the startup disk selection interface. Select the USB stick, which is shown as &amp;ldquo;Install Big Sur&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;This will boot into the Big Sur recovery mode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use disk utility to erase the disk. Use security options to securely erase the data from the disk if I plan to sell the Mac or send it to the genius bar.&lt;/li&gt;
&lt;li&gt;Quit disk utility and choose to install Big Sur from the menu.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://support.apple.com/en-gb/HT201372"&gt;How to create a bootable installer for macOS – Apple Support&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://support.apple.com/en-us/HT208198"&gt;About Startup Security Utility - Apple Support&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/macos/">macOS</category><category domain="https://blog.iany.me/tags/system-admin/">System Admin</category></item><item><title>Gotchas to Publish Rust Crates in a Workspace</title><link>https://blog.iany.me/2020/10/gotchas-to-publish-rust-crates-in-a-workspace/</link><pubDate>Sun, 11 Oct 2020 12:03:32 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/10/gotchas-to-publish-rust-crates-in-a-workspace/</guid><description>&lt;p&gt;It is recommended to split a huge Rust project into crates and manage them in a workspace. I&amp;rsquo;m currently working on a project which consists of about 60 crates. It works well so far until I try to publish these crates to crates.io.&lt;/p&gt;
&lt;p&gt;I will list the problems I have met and solutions or workarounds I have adopted.&lt;/p&gt;
&lt;h2 id="update-cargotoml"&gt;Update &lt;em&gt;Cargo.toml&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;Crates.io has some requirements on Cargo.toml files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fields like license, authors, description, homepage, and repository are required.&lt;/li&gt;
&lt;li&gt;Local crates via &lt;code&gt;path&lt;/code&gt; must specify the version as well so the dependencies can be resolved via crates.io.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command &lt;code&gt;cargo publish --dry-run&lt;/code&gt; will verify whether the file Cargo.toml is valid.&lt;/p&gt;
&lt;h2 id="theres-no-cargo-publish---all"&gt;There&amp;rsquo;s No &lt;code&gt;cargo publish --all&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Cargo &lt;a href="https://github.com/rust-lang/cargo/issues/1169"&gt;does not support publishing all local packages yet&lt;/a&gt;. It means that I have to cd into each crate and publish them separately.&lt;/p&gt;
&lt;p&gt;There are two major issues here.&lt;/p&gt;
&lt;p&gt;The first, if a crate &lt;code&gt;foo&lt;/code&gt; depends on &lt;code&gt;bar&lt;/code&gt;, a version of &lt;code&gt;bar&lt;/code&gt; satisfying &lt;code&gt;foo&lt;/code&gt;&amp;rsquo;s requirement must be available in crates.io before publishing &lt;code&gt;foo&lt;/code&gt;. In the project I&amp;rsquo;m working on, all crates share the same version, and they lock the local dependencies using the exact version such as &lt;code&gt;bar = &amp;quot;= 0.0.1&amp;quot;&lt;/code&gt;. If I want to publish these crates, I must topologically sort them by dependencies first and publish them in that order. The newly published crate has a delay of up to 10 seconds before it is searchable. In my publish script, I&amp;rsquo;ll retry several times when &lt;code&gt;cargo&lt;/code&gt; complaints that it fails to resolve a dependency.&lt;/p&gt;
&lt;p&gt;Second, it is not atomic to publish the whole workspace. If I successfully published &lt;code&gt;bar 0.0.1&lt;/code&gt; and later fails to publish &lt;code&gt;foo 0.0.1&lt;/code&gt; because of a bug in &lt;code&gt;bar 0.0.1&lt;/code&gt;, I have to yank &lt;code&gt;bar 0.0.1&lt;/code&gt;, bump the version, and re-publish both &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="cyclic-dependencies"&gt;Cyclic Dependencies&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;dev-dependencies&lt;/code&gt; may introduce cyclic dependencies. Take the example again that the crate &lt;code&gt;foo&lt;/code&gt; depends on &lt;code&gt;bar&lt;/code&gt;. This time the test cases of &lt;code&gt;bar&lt;/code&gt; depend on &lt;code&gt;foo&lt;/code&gt;. Cargo can resolve these cyclic dependencies because it does not need the &lt;code&gt;dev-dependencies&lt;/code&gt; to build both &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt;, so it can build &lt;code&gt;bar&lt;/code&gt;, &lt;code&gt;foo&lt;/code&gt;, and then &lt;code&gt;bar&lt;/code&gt; test cases in order.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;cargo publish&lt;/code&gt; requires both dependencies and &lt;code&gt;dev-dependencies&lt;/code&gt; are available in crates.io, now cyclic dependencies will cause problems.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s tedious to arrange the crates to resolve the cyclic dependencies, so I adopted &lt;a href="https://github.com/rust-lang/cargo/issues/4242"&gt;this workaround&lt;/a&gt; to remove all the &lt;code&gt;dev-dependencies&lt;/code&gt; before publish and run &lt;code&gt;cargo publish --allow-dirty&lt;/code&gt; to ignore the dirty git working directory.&lt;/p&gt;
&lt;h2 id="it-is-very-slow-to-publish"&gt;It Is Very Slow to Publish&lt;/h2&gt;
&lt;p&gt;Finally, it is really slow to publish 60 crates in a big project. It seems that they will not share the target directory and each crate uses its own directory inside &lt;code&gt;target/publish&lt;/code&gt;. If the workspace has three crates, &lt;code&gt;foo&lt;/code&gt;, &lt;code&gt;bar&lt;/code&gt;, &lt;code&gt;baz&lt;/code&gt;, where &lt;code&gt;foo&lt;/code&gt; depends on &lt;code&gt;bar&lt;/code&gt;, and &lt;code&gt;bar&lt;/code&gt; depends on &lt;code&gt;baz&lt;/code&gt;, cargo will take quadratic time to publish all the crates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build &lt;code&gt;baz&lt;/code&gt; and publish &lt;code&gt;baz&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build &lt;code&gt;baz&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt;, then publish &lt;code&gt;bar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build &lt;code&gt;baz&lt;/code&gt;, &lt;code&gt;bar&lt;/code&gt;, and &lt;code&gt;foo&lt;/code&gt;, then publish &lt;code&gt;foo&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;target/publish&lt;/code&gt; also takes a lot of disk space. After I published 60 crates, the folder occupied about 80G storage. If the publish host has limited disk space, the script must clean up the folder regularly.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/cargo/">Cargo</category><category domain="https://blog.iany.me/tags/rust/">Rust</category></item><item><title>Monero Dynamic Block Weight</title><link>https://blog.iany.me/2020/09/monero-dynamic-block-weight/</link><pubDate>Sat, 05 Sep 2020 17:52:10 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/09/monero-dynamic-block-weight/</guid><description>&lt;p&gt;Monero dynamic block weight is an interesting design. Instead of a fixed supply of transaction space, Monero uses the history blocks to determine the dynamic limit and use the median to control the increasing pace.&lt;/p&gt;
&lt;p&gt;The major reference of this article is chapter 7.3 Money supply in the book &lt;em&gt;Zero to Monero: Second Edition&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="block-weight-limit"&gt;Block Weight Limit&lt;/h2&gt;
&lt;p&gt;The current block&amp;rsquo;s &lt;code&gt;cumulative_weights_median&lt;/code&gt; is the base limit for the next block. The max block weight of the next block is &lt;code&gt;2 ∗ cumulative_weights_median&lt;/code&gt; and the block reward decays when the block weight is greater than &lt;code&gt;cumulative_weights_median&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Block Reward decays when the weight is greater than `cumulative_weights_median`" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/09/monero-dynamic-block-weight/block_reward_hu_a4d65800908d1a14.png" srcset="https://blog.iany.me/2020/09/monero-dynamic-block-weight/block_reward_hu_f563f3d572f4eecb.png 400w, https://blog.iany.me/2020/09/monero-dynamic-block-weight/block_reward_hu_a4d65800908d1a14.png 640w" sizes="(max-width: 400px) 100vw, 640px" /&gt;
&lt;figcaption &gt;Block Reward decays when the weight is greater than &lt;code&gt;cumulative_weights_median&lt;/code&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The definition of &lt;code&gt;cumulative_weights_median&lt;/code&gt; is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cumulative_weights_median = max{
300kB,
min{
max{
300kB,
median_100blocks_weights
},
50 ∗ effective_longterm_median
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The item &lt;code&gt;300kB&lt;/code&gt; is the lower bound of &lt;code&gt;cumulative_weights_median&lt;/code&gt;, which can be removed to simplify the formula.&lt;/p&gt;
&lt;p&gt;The term &lt;code&gt;effective_longterm_median&lt;/code&gt; changes slowly and can be considered as a constant here. It acts as the upper bound.&lt;/p&gt;
&lt;p&gt;The value of &lt;code&gt;cumulative_weights_median&lt;/code&gt; indeed is the median of the last 100 blocks weights clamped between 300kB and &lt;code&gt;50 * effective_longterm_median&lt;/code&gt;. In turn, the block weight is restricted by &lt;code&gt;cumulative_weights_median&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The base limit &lt;code&gt;cumulative_weights_median&lt;/code&gt; increases when there are at least 50 in the recent 100 blocks which weight is greater than &lt;code&gt;cumulative_weights_median&lt;/code&gt;, indicating that at least half of the miners are willing to take the block reward penalty. Miners are incentivized to take the penalty because the transaction fees can cover the loss.&lt;/p&gt;
&lt;p&gt;If the network runs at full load, &lt;code&gt;cumulative_weights_median&lt;/code&gt; doubles every 50 blocks and will reach the upper bound &lt;code&gt;50 * effective_longterm_median&lt;/code&gt; eventually.&lt;/p&gt;
&lt;p&gt;Pay attention that &lt;code&gt;cumulative_weights_median&lt;/code&gt; falls immediately when the network load drops. If there are 50 in the last 100 blocks which weight is less than 300kB, &lt;code&gt;cumulative_weights_median&lt;/code&gt; will become 300kB. Maintaining &lt;code&gt;cumulative_weights_median&lt;/code&gt; at a value higher than 300kB requires continuous transaction traffic.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="An example of how `cumulative_weights_median` changes" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/09/monero-dynamic-block-weight/cumulative_weights_median_hu_45a0a734eb11529e.png" srcset="https://blog.iany.me/2020/09/monero-dynamic-block-weight/cumulative_weights_median_hu_a0c29b0f14ec132b.png 400w, https://blog.iany.me/2020/09/monero-dynamic-block-weight/cumulative_weights_median_hu_45a0a734eb11529e.png 640w" sizes="(max-width: 400px) 100vw, 640px" /&gt;
&lt;figcaption &gt;An example of how &lt;code&gt;cumulative_weights_median&lt;/code&gt; changes&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="long-term-limit"&gt;Long Term Limit&lt;/h2&gt;
&lt;p&gt;The value of &lt;code&gt;effective_longterm_median&lt;/code&gt; caps the max block weight in the short term. As the name suggests, it is the median of the &lt;code&gt;longterm_block_weight&lt;/code&gt; of the last 100,000 blocks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;effective_longterm_median = max { 300kB, median_100000blocks_longterm_weights }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The definition &lt;code&gt;longterm_block_weight&lt;/code&gt; is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;min { block_weight, 1.4 ∗ previous_effective_longterm_median }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The value of &lt;code&gt;effective_longterm_median&lt;/code&gt; is the median of the last 100,000 blocks weights clamped between 300kB and &lt;code&gt;1.4 * previous effective_longterm_median&lt;/code&gt;. Same with &lt;code&gt;cumulative_weights_median&lt;/code&gt;, it increases slowly and drops sharply.
If all the blocks weights are greater than &lt;code&gt;1.4 ∗ previous_effective_longterm_median&lt;/code&gt;, &lt;code&gt;effective_longterm_median&lt;/code&gt; rises by 40% every 50,000 blocks.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/blockchain/">Blockchain</category><category domain="https://blog.iany.me/tags/crypto-economics/">Crypto Economics</category><category domain="https://blog.iany.me/tags/monero/">Monero</category></item><item><title>Bitcoin Core Fee Estimate Algorithm</title><link>https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/</link><pubDate>Sun, 16 Aug 2020 14:38:10 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/</guid><description>&lt;p&gt;In bitcoin, the total size of transactions added to the chain in a specific time is limited. This creates a fee market. Transactions with a higher fee rate are likely to be confirmed more quickly. A good fee estimator predicates which fee rate to pay where there is a high probability the transaction will be confirmed into the chain within the target period.&lt;/p&gt;
&lt;p&gt;Bitcoin core has the builtin support to estimate the fee rate. Understanding its algorithm can help us to migrate it and improve it.&lt;/p&gt;
&lt;p&gt;This first part of this article introduces the core estimation algorithm. The algorithm determines which statistics data to track, and the second part shows how Bitcoin Core tracks them. The last part describes the extensions to the core algorithm for better performance.&lt;/p&gt;
&lt;p&gt;The main reference of the algorithm is the &lt;a href="https://github.com/bitcoin/bitcoin/blob/master/src/policy/fees.cpp"&gt;source code&lt;/a&gt; and &lt;a href="https://gist.github.com/morcos/d3637f015bc4e607e1fd10d8351e9f41"&gt;the post&lt;/a&gt; from the author.&lt;/p&gt;
&lt;h2 id="the-core-estimation-algorithm"&gt;The Core Estimation Algorithm&lt;/h2&gt;
&lt;p&gt;The algorithm must predicate which fee rate to pay where there is a high probability the transaction will be confirmed into the chain within the target period.&lt;/p&gt;
&lt;p&gt;The basic idea is checking whether there is &lt;strong&gt;enough fraction&lt;/strong&gt; of transactions with &lt;strong&gt;similar fee rate&lt;/strong&gt; which has been confirmed within the target period.&lt;/p&gt;
&lt;h3 id="fee-rate-bucket"&gt;Fee Rate Bucket&lt;/h3&gt;
&lt;p&gt;Bitcoin groups transactions into different buckets by fee rate to provide statistics for transactions with a similar fee rate.&lt;/p&gt;
&lt;p&gt;The buckets are distributed exponentially. The buckets are configured via three parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;U: the first bucket upper bound.&lt;/li&gt;
&lt;li&gt;S: spacing. The latter bucket upper bound is S times of the former bucket upper bound.&lt;/li&gt;
&lt;li&gt;B: the total number of buckets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The B buckets are separated by B - 1 upper bounds, which split the fee rate range from 0 to infinity into B segments, each segment corresponds to a bucket. The first upper bound is U, and the other bound is S times of its preceding bound.&lt;/p&gt;
&lt;p&gt;The following is an example of 4 buckets.&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Fee Rate Buckets" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/fee-rate-buckets_hu_fc8ac6e2d9cee679.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/fee-rate-buckets_hu_fc4d5bee81c0addf.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/fee-rate-buckets_hu_fc8ac6e2d9cee679.png 663w" sizes="(max-width: 400px) 100vw, 663px" /&gt;
&lt;figcaption &gt;Fee Rate Buckets&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="confirmation-fraction"&gt;Confirmation Fraction&lt;/h3&gt;
&lt;p&gt;Bitcoin tracks the number three kinds of transactions in each bucket to compute the confirmation fraction.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unconfirmed: the transactions remained in the memory pool. The lifespan of an unconfirmed transaction is the current chain height minus the chain height when the transaction was added to the pool.&lt;/li&gt;
&lt;li&gt;Confirmed: the transactions included in the chain. The lifespan of a confirmed transaction is the number of the block containing the transaction minus the chain height when the transaction was added to the pool.&lt;/li&gt;
&lt;li&gt;Failed: the transactions removed from the memory pool but not included in the chain. The lifespan of a failed transaction is the chain height when it is removed minus the chain height when it was added.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Further splitting transactions lifespan by the target period X blocks gives the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;U: Unconfirmed and the lifespan is greater than or equal to X blocks&lt;/li&gt;
&lt;li&gt;C: Confirmed and the lifespan is less than or equal to X blocks&lt;/li&gt;
&lt;li&gt;T: Total number of confirmed transactions&lt;/li&gt;
&lt;li&gt;F: Failed and the lifespan is greater than X blocks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The confirmation fraction is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Confirmation Fraction = C / (U + T + F)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Confirmation Fraction" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/confirmation-fraction_hu_2b89078285b007d.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/confirmation-fraction_hu_b06b56e3a2d2c4c0.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/confirmation-fraction_hu_2b89078285b007d.png 561w" sizes="(max-width: 400px) 100vw, 561px" /&gt;
&lt;figcaption &gt;Confirmation Fraction&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The estimator needs to find the bucket in which confirmation fraction is larger than a specific threshold.&lt;/p&gt;
&lt;h3 id="buckets-scan"&gt;Buckets Scan&lt;/h3&gt;
&lt;p&gt;With the tracking data, the estimation algorithm scans the buckets to find an optimal bucket in which confirmation fraction is larger than a specific threshold for the given confirmation target.&lt;/p&gt;
&lt;p&gt;There are two modes to find the optimal bucket which fee rate meets the confirmation target.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Greater All Passed&lt;/em&gt; looks for the lowest fee rate that all higher values pass. It starts at the bucket with the highest fee rate and looks at successively smaller buckets until reaching failure. The best bucket is the last passed bucket in the scan.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Less All Failed&lt;/em&gt; looks for the highest fee rate that all lower values fail. It starts at the bucket with the lowest fee rate and looks at successively larger buckets until passing the threshold. The best bucket is the last failed bucket in the scan.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not every bucket has enough data points, Bitcoin will merge consecutive buckets until there are enough transactions, a.k.a, the total number of confirmed transactions are larger than a specific threshold. At the end of the scan, if all the remaining blocks combined have no enough data points, the estimation fails.&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Buckets Merge" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/scan-and-merge_hu_fceee932da9dd280.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/scan-and-merge_hu_4821f7fe89404e0.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/scan-and-merge_hu_fceee932da9dd280.png 612w" sizes="(max-width: 400px) 100vw, 612px" /&gt;
&lt;figcaption &gt;Buckets Merge&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the found best bucket is a merged bucket range, the one containing the transaction with the median fee rate is selected. And the estimated fee rate is the average fee rate of all the confirmed transactions in the bucket.&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Median Bucket" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/median-bucket_hu_b7c0c9dd644f69ba.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/median-bucket_hu_5399fabd940ff26b.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/median-bucket_hu_b7c0c9dd644f69ba.png 510w" sizes="(max-width: 400px) 100vw, 510px" /&gt;
&lt;figcaption &gt;Median Bucket&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="tracking"&gt;Tracking&lt;/h2&gt;
&lt;p&gt;From the analysis in the previous chapter, given a specific bucket, the estimator has to use the following statistic metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;U: Unconfirmed and the lifespan is greater than or equal to X blocks&lt;/li&gt;
&lt;li&gt;C: Confirmed and the lifespan is less than or equal to X blocks&lt;/li&gt;
&lt;li&gt;T: Total number of confirmed transactions&lt;/li&gt;
&lt;li&gt;F: Failed and the lifespan is greater than X blocks.&lt;/li&gt;
&lt;li&gt;R: Sum of fee rates of all the confirmed transactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C and R are required to compute the average fee rate in the best bucket which equals to R / C.&lt;/p&gt;
&lt;p&gt;For performance issues, Bitcoin tracks up to N blocks and uses a scale S to aggregate data. Pay attention that N and S will be used in the counters defined in the following chapters.&lt;/p&gt;
&lt;p&gt;The tracking is performed on each chain height once thus ignores the fork chain which has the same height as a tracked chain.&lt;/p&gt;
&lt;p&gt;The estimator needs to monitor following three kinds of events to track the statistics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A new block is added to the chain.&lt;/li&gt;
&lt;li&gt;A new transaction is added to the pool.&lt;/li&gt;
&lt;li&gt;A transaction is removed from pool and the reason is not that it has been added to the chain.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="unconfirmed-transactions"&gt;Unconfirmed Transactions&lt;/h3&gt;
&lt;p&gt;Bitcoin uses a data structure to track the number of unconfirmed transactions for each lifespan between 0 (inclusively) and N (exclusively) and the number of all unconfirmed transactions which lifespan is greater or equal to N. It can give the answer to &lt;em&gt;U: Unconfirmed and the lifespan is greater than or equal to X blocks&lt;/em&gt;, when X is not greater than N.&lt;/p&gt;
&lt;p&gt;To avoid moving data around, the first N counters 0 to N-1 are organized as a ring. Assume that the current chain height is H, the transactions added at the height &lt;code&gt;H - i&lt;/code&gt; belongs to counter r where r is the remainder after dividing &lt;code&gt;H - i&lt;/code&gt; by N.&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Tracking Unconfirmed Transactions" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-unconfirmed-transactions_hu_b97f48e9a9653fda.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-unconfirmed-transactions_hu_314ceafb353d95b9.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-unconfirmed-transactions_hu_b97f48e9a9653fda.png 549w" sizes="(max-width: 400px) 100vw, 549px" /&gt;
&lt;figcaption &gt;Tracking Unconfirmed Transactions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Rotating Unconfirmed Transactions" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/rotating-unconfirmed-transactions-counters_hu_b76c7929aff3a66c.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/rotating-unconfirmed-transactions-counters_hu_b572d169abea3b1a.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/rotating-unconfirmed-transactions-counters_hu_b76c7929aff3a66c.png 799w" sizes="(max-width: 400px) 100vw, 799px" /&gt;
&lt;figcaption &gt;Rotating Unconfirmed Transactions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The counter N, the last counter, records the number of unconfirmed transactions which lifespan is greater than or equal to N. When a counter in the ring is about to be replaced, the count is added to the counter N.&lt;/p&gt;
&lt;p&gt;See &lt;code&gt;TxConfirmStats::unconfTxs&lt;/code&gt; and &lt;code&gt;TxConfirmStats::oldUnconfTxs&lt;/code&gt; in the source code.&lt;/p&gt;
&lt;h3 id="failed-transactions"&gt;Failed Transactions&lt;/h3&gt;
&lt;p&gt;There are N / S counters, 0 to N/S-1, for each fee rate bucket to track failed transactions number. The counter i tracks the removed transactions in which lifespan is greater than &lt;code&gt;i * S + 1&lt;/code&gt;. The counter &lt;code&gt;ceil(X / S) - 1&lt;/code&gt; gives an approximate answer to &lt;em&gt;F: Failed and the lifespan is greater than X blocks&lt;/em&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Tracking Failed Transactions" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-failed-transactions_hu_4021870c0380f136.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-failed-transactions_hu_46d74e60534995cc.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-failed-transactions_hu_4021870c0380f136.png 612w" sizes="(max-width: 400px) 100vw, 612px" /&gt;
&lt;figcaption &gt;Tracking Failed Transactions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The counters are keeping moving average instead of the total count, which means at the beginning of each new chain height, the old counter decays with a configured percentage &lt;code&gt;decay&lt;/code&gt; which is less than 1.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;count = count * decay
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Decaying Moving Average" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/decaying-moving-average_hu_4d1ef26cd96bd8ac.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/decaying-moving-average_hu_bd6ed411330e5621.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/decaying-moving-average_hu_4d1ef26cd96bd8ac.png 487w" sizes="(max-width: 400px) 100vw, 487px" /&gt;
&lt;figcaption &gt;Decaying Moving Average&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;See &lt;code&gt;TxConfirmStats::failAvg&lt;/code&gt; in the source code.&lt;/p&gt;
&lt;h3 id="confirmed-transactions"&gt;Confirmed Transactions&lt;/h3&gt;
&lt;p&gt;There are N / S counters, 0 to N/S-1, for each fee rate bucket to track confirmed transactions number. The counter i tracks the confirmed transactions which lifespan is less than or equal to &lt;code&gt;(i + 1) * S&lt;/code&gt;. The counter &lt;code&gt;ceil(X / S) - 1&lt;/code&gt; gives an approximate answer to &lt;em&gt;C: Confirmed and the lifespan is less than or equal to X blocks&lt;/em&gt;&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Tracking Confirmed Transactions" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-confirmed-transactions_hu_128e2053a2682e6.png" srcset="https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-confirmed-transactions_hu_36ff97c561ae2a15.png 400w, https://blog.iany.me/2020/08/bitcoin-core-fee-estimate-algorithm/tracking-confirmed-transactions_hu_128e2053a2682e6.png 634w" sizes="(max-width: 400px) 100vw, 634px" /&gt;
&lt;figcaption &gt;Tracking Confirmed Transactions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There are two extra counters, one tracks the total number of confirmed transactions and another tracks the total fee rates of all confirmed transactions. They provide answers to &lt;em&gt;T: Total number of confirmed transactions&lt;/em&gt;, and &lt;em&gt;R: Sum of fee rates of all the confirmed transactions&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;All the N / S and two extra counters for confirmed transactions, like the failed transactions are keeping the decaying moving average with the same decay percentage.&lt;/p&gt;
&lt;p&gt;See &lt;code&gt;TxConfirmStats::confAvg&lt;/code&gt;, &lt;code&gt;TxConfirmStats::txCtAvg&lt;/code&gt; and &lt;code&gt;TxConfirmStats::avg&lt;/code&gt; in the source code.&lt;/p&gt;
&lt;h2 id="extensions"&gt;Extensions&lt;/h2&gt;
&lt;p&gt;Bitcoin use three data sets with different scales and maximum tracked confirmations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Short: The scale is 1. It tracks 12 blocks and decays the moving average 96.2% on each new height.&lt;/li&gt;
&lt;li&gt;Med: The scale is 2. It tracks 48 blocks and decays the moving average 99.52%.&lt;/li&gt;
&lt;li&gt;Long: The scale is 24. It tracks 1008 blocks and decays the moving average 99.931%.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Longer time horizon can estimate the fee rate with a high confirmation target but is less accurate for a low target.&lt;/p&gt;
&lt;p&gt;Given a required confirmation target X, bitcoin chooses the data set with the shortest tracking confirmations which are greater than or equal to X. For example, it uses Short when X is 12, and Med when X is 13.&lt;/p&gt;
&lt;p&gt;The first extension, named as &lt;code&gt;estimateCombinedFee&lt;/code&gt;, is that Bitcoin also checks the shorter time horizons and gives the lowest estimation as the answer. For example, when X is 49, the estimator will run the core algorithm three times:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses data set Long with target 49&lt;/li&gt;
&lt;li&gt;Uses data set Med with target 48&lt;/li&gt;
&lt;li&gt;Uses data set Short with target 12.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second extension is that Bitcoin will run &lt;code&gt;estimateCombinedFee&lt;/code&gt; with 3 different configurations and return the largest estimation given the confirmation target X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Half Estimation runs &lt;code&gt;estimateCombinedFee&lt;/code&gt; with confirmation target X/2 and confirmation fraction threshold 60%.&lt;/li&gt;
&lt;li&gt;Actual Estimation runs &lt;code&gt;estimateCombinedFee&lt;/code&gt; with confirmation target X and confirmation fraction threshold 85%.&lt;/li&gt;
&lt;li&gt;Double Estimation runs &lt;code&gt;estimateCombinedFee&lt;/code&gt; with confirmation target 2X and confirmation fraction threshold 95%.&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/algorithm/">Algorithm</category><category domain="https://blog.iany.me/tags/bitcoin/">Bitcoin</category><category domain="https://blog.iany.me/tags/fee-market/">Fee Market</category></item><item><title>Yubico for Windows</title><link>https://blog.iany.me/2020/07/yubico-for-windows/</link><pubDate>Sat, 11 Jul 2020 16:38:25 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/07/yubico-for-windows/</guid><description>&lt;p&gt;This post records how I set up Yubico Key in Windows, so I’ll not delve into too much details. I have the model YubiKey 5 NFC. I frequently use 2 GPG keys stored in the key, one for encryption, another for SSH authentication.&lt;/p&gt;
&lt;p&gt;The GPG encryption part is simple, GnuPG just works. Using the stored GPG key for SSH is a bit complex, because it requires collaboration between GnuPG and the SSH client. &lt;del&gt;After experiment many different solutions, I decide to use the simplest one, using putty/plink as the SSH client and enabling thepageant support in GnuPG.&lt;/del&gt; See &lt;a href="https://blog.iany.me/2021/02/ssh-authentication-using-a-yubikey-on-windows-and-the-openssh-client/"&gt;♯ SSH Authentication Using a YubiKey on Windows And the OpenSSH Client&lt;/a&gt; how to use OpenSSH client with YubiKey.&lt;/p&gt;
&lt;h2 id="windows"&gt;Windows&lt;/h2&gt;
&lt;p&gt;I use &lt;a href="https://scoop.sh"&gt;scoop&lt;/a&gt; to manage apps in Windows. My configuration requires gpg and putty, where putty is in the extra buckets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scoop bucket add extras
scoop install gpg putty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tells git to use gpg and putty&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global gpg.program (Get-Command -Name 'gpg.exe').Source
$SSHPath = (Get-Command -Name 'plink.exe').Source
[Environment]::SetEnvironmentVariable('GIT_SSH', $SSHPath, 'User')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit &lt;code&gt;gpg-agent.conf&lt;/code&gt; in folder &lt;code&gt;$(scoop prefix gpg)\home\&lt;/code&gt;, enable putty support&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;enable-putty-support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the way, the putty support means that gpg agent will also act as pageant, so there&amp;rsquo;s no need and it is also forbidden to run pageant manually. If pageant is running, quit it first then restart gpg agent.&lt;/p&gt;
&lt;p&gt;Since my GPG keys are already stored in the YubiKey, I just need to export the public keys from somewhere and import them into the Windows host.&lt;/p&gt;
&lt;p&gt;Restart gpg agent to check whether the keys are recognized:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg-connect-agent killagent /bye
gpg --card-status
gpg -K
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;gpg --card-status&lt;/code&gt; complains that it cannot find the key card, refer to &lt;a href="https://support.yubico.com/hc/en-us/articles/360013714479-Troubleshooting-Issues-with-GPG"&gt;this article&lt;/a&gt; to save &lt;code&gt;reader-port Yubico Yubi&lt;/code&gt; in the file ``$(scoop prefix gpg)\home\scdaemon.conf` and try again.&lt;/p&gt;
&lt;h2 id="wsl"&gt;WSL&lt;/h2&gt;
&lt;p&gt;My solution to use the YubiKey in WSL is straightforward. Just use the Windows executables in WSL.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ln -snf &amp;quot;$(which plink.exe)&amp;quot; &amp;quot;$HOME/bin/ssh&amp;quot;
ln -snf &amp;quot;$(which pscp.exe)&amp;quot; &amp;quot;$HOME/bin/scp&amp;quot;
ln -snf &amp;quot;$(which gpg.exe)&amp;quot; &amp;quot;$HOME/bin/gpg&amp;quot;
git config --global core.sshCommand &amp;quot;$(which plink.exe)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/windows/">Windows</category><category domain="https://blog.iany.me/tags/yubikey/">YubiKey</category></item><item><title>Memory Tracing in Windows</title><link>https://blog.iany.me/2020/06/memory-tracing-in-windows/</link><pubDate>Sat, 27 Jun 2020 16:52:48 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/06/memory-tracing-in-windows/</guid><description>&lt;p&gt;The article mainly refers &lt;a href="https://docs.microsoft.com/en-us/windows-hardware/test/wpt/memory-footprint-optimization-exercise-2"&gt;Exercise 2 - Track User Mode Process Allocations | Microsoft Docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="1-install-windows-performance-recorder-and-analyzer"&gt;1. Install Windows Performance Recorder and Analyzer&lt;/h2&gt;
&lt;p&gt;Download and install ADK from &lt;a href="https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install"&gt;here&lt;/a&gt;. The windows build version can be checked by running &amp;ldquo;winver&amp;rdquo; via &lt;kbd&gt;Win+r&lt;/kbd&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s sufficient to only install the component &amp;ldquo;Windows Performance Toolkit&amp;rdquo;&lt;/p&gt;
&lt;h2 id="2-enable-tracing-the-target-process"&gt;2. Enable Tracing the Target Process&lt;/h2&gt;
&lt;p&gt;For example, to enable tracking ckb&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;reg add &amp;quot;HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ckb.exe&amp;quot; /v TracingFlags /t REG_DWORD /d 1 /f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pay attention to the executable name &amp;ldquo;ckb.exe&amp;rdquo; used in the command. The command requires administrator permission. A simple way is starting an admin PowerShell using keyboard shortcuts &lt;kbd&gt;Win+x a&lt;/kbd&gt;.&lt;/p&gt;
&lt;h2 id="3-build-the-process-with-debug-symbols"&gt;3. Build the Process With Debug Symbols&lt;/h2&gt;
&lt;p&gt;In Rust, this can be enabled by adding &lt;code&gt;debug = true&lt;/code&gt; to Cargo.toml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[profile.release]
debug = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;cargo build --release&lt;/code&gt; will also creates a pdb file, which contains the debug symbols.&lt;/p&gt;
&lt;h2 id="4-start-recording"&gt;4. Start Recording&lt;/h2&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Wpr-Start-Recording" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpr-start-recording_hu_ccf185ff82e58d02.png" srcset="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpr-start-recording_hu_d25db002c25ca68b.png 400w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpr-start-recording_hu_fc9fb89b51107239.png 800w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpr-start-recording_hu_ccf185ff82e58d02.png 1174w" sizes="(max-width: 800px) 100vw, 1174px" /&gt;
&lt;/figure&gt;
&lt;p&gt;Start Windows Performance Recorder from Start Menu. Check &amp;ldquo;Heap usage&amp;rdquo; and &amp;ldquo;VirtualAlloc usage&amp;rdquo; in the resource analysis. Then click the &amp;ldquo;Start&amp;rdquo; button.&lt;/p&gt;
&lt;h2 id="5-launch-process"&gt;5. Launch Process&lt;/h2&gt;
&lt;p&gt;Launch the process for a while, like 3 minutes. On my 16G memory machine, Windows Performance Analyzer seems cannot load too long recording. It will complain there is no enough memory.&lt;/p&gt;
&lt;h2 id="6-stop-recording-and-save-the-report"&gt;6. Stop Recording and Save the Report&lt;/h2&gt;
&lt;p&gt;Reactivate Windows Performance Recorder and click the &amp;ldquo;Save&amp;rdquo; button.&lt;/p&gt;
&lt;h2 id="7-open-the-report-in-windows-performance-analyzer"&gt;7. Open the Report in Windows Performance Analyzer&lt;/h2&gt;
&lt;p&gt;Open the saved report file in Windows Performance Analyzer.&lt;/p&gt;
&lt;p&gt;Expand the Memory in the left side bar and drag &amp;ldquo;VirtualAlloc Commit LifeTimes&amp;rdquo; chart to the Analysis tab.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Wpa-Analyze-Virtualalloc" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-analyze-virtualalloc_hu_37ee6c9f8f6d17ea.png" srcset="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-analyze-virtualalloc_hu_cda3e35360cf632b.png 400w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-analyze-virtualalloc_hu_55737550ad6ee291.png 800w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-analyze-virtualalloc_hu_3f93c8c50147b250.png 1200w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-analyze-virtualalloc_hu_f107fee27114efe.png 1600w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-analyze-virtualalloc_hu_37ee6c9f8f6d17ea.png 1931w" sizes="(max-width: 1600px) 100vw, 1931px" /&gt;
&lt;/figure&gt;
&lt;p&gt;VirtualAlloc Commit LifeTimes tracking records all the heap memory allocation, when they are allocated and freed. It can help us to find out who has allocated memories but not return them.&lt;/p&gt;
&lt;p&gt;First filter the the result to only show the target process. Find the process in the table and apply &amp;ldquo;Filter to Selection&amp;rdquo; in the right click context menu.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Wpa-Filter-to-Selection" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-filter-to-selection_hu_7fbd78fd8a5c8c4b.png" srcset="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-filter-to-selection_hu_f53d95fec4beecbb.png 400w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-filter-to-selection_hu_dfcc486496df9bc7.png 800w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-filter-to-selection_hu_ec4cb3e50b1e2dc2.png 1200w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-filter-to-selection_hu_7fd2dbebac6e1223.png 1600w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-filter-to-selection_hu_7fbd78fd8a5c8c4b.png 1930w" sizes="(max-width: 1600px) 100vw, 1930px" /&gt;
&lt;/figure&gt;
&lt;p&gt;The key columns of interest are the following (&lt;a href="https://docs.microsoft.com/en-us/windows-hardware/test/wpt/memory-footprint-optimization-exercise-2"&gt;source&lt;/a&gt;):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Process&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The name of the process that performs memory allocations through &lt;strong&gt;VirtualAlloc&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Commit Stack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The call stack that shows the code path leading to memory being allocated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Commit Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The timestamp of when memory was allocated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decommit Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The timestamp of when memory was freed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Impacting Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The size of outstanding allocations or the size difference between the start and end of the selected time interval. This size adjusts based on the selected view port.The &lt;strong&gt;Impacting Size&lt;/strong&gt; value will be zero if all memory allocated by a process is freed by the end of the visualized interval in &lt;strong&gt;WPA.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The cumulative sum of all allocation during the selected time interval.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;New columns can be added by right click the existing column header. The lines are automatically grouped by the columns that on the left of the yellow vertical bar.&lt;/p&gt;
&lt;p&gt;However, I find out that it is hard to locate the leaf nodes when dragging the Commit Stack column to the left.&lt;/p&gt;
&lt;p&gt;Sometimes, it is useful to zoom into a specfic time range, such as a memory surge to see the impacting allocations.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Wpa-Zoom" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-zoom_hu_5ac94ad967b9b44a.png" srcset="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-zoom_hu_a03a2a7329b6c935.png 400w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-zoom_hu_4991daeefb5dcf8a.png 800w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-zoom_hu_4586572842cb1669.png 1200w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-zoom_hu_1d42667e3e3d1ede.png 1600w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-zoom_hu_b67007083718d51e.png 2000w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-zoom_hu_5ac94ad967b9b44a.png 2078w" sizes="(max-width: 2000px) 100vw, 2078px" /&gt;
&lt;/figure&gt;
&lt;p&gt;The Commit Stack does not loads debug symbols by default. First configure the symbols paths to include target process pdb file via menu item &amp;ldquo;Trace ➤ Configure Symbols Paths&amp;rdquo;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Wpa-Configure-Symbols-Paths" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-configure-symbols-paths_hu_eaf01523e7ede57.png" srcset="https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-configure-symbols-paths_hu_9822f64bf7122e09.png 400w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-configure-symbols-paths_hu_295689473d017f36.png 800w, https://blog.iany.me/2020/06/memory-tracing-in-windows/wpa-configure-symbols-paths_hu_eaf01523e7ede57.png 878w" sizes="(max-width: 800px) 100vw, 878px" /&gt;
&lt;/figure&gt;
&lt;p&gt;Windows Performance Recorder also automatically saves system libraries debug symbols in a &amp;ldquo;NGENPDB&amp;rdquo; directory. Windows Performance Analyzer will automatically add it. But if the recording file is too large, and the computer is not powerful enought, the computer may stuck at loading symbols. If so, uncheck the &amp;ldquo;NGENPDB&amp;rdquo; in the symbols paths.&lt;/p&gt;
&lt;p&gt;After setting up the paths, select menu &amp;ldquo;Trace ➤ Load Symbols&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Now Commit Stack will show the symbols.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/performance/">Performance</category><category domain="https://blog.iany.me/tags/windows/">Windows</category></item><item><title>Move WSL to Another Drive</title><link>https://blog.iany.me/2020/06/move-wsl-to-another-drive/</link><pubDate>Fri, 26 Jun 2020 16:51:34 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/06/move-wsl-to-another-drive/</guid><description>&lt;p&gt;Following example moves the Ubuntu distribution to disk &lt;code&gt;D:\WSL\Ubuntu&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;. Install Ubuntu in the Microsoft Store. Launch it to initialize the default instance. Create the user used in Ubuntu as prompted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;. Export the instance and import into the target directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd D:\
mkdir WSL
cd WSL
mkdir Ubuntu
wsl --export Ubuntu .\Ubuntu\ext4.vhdx --vhd
wsl --unregister Ubuntu
wsl --import-in-place Ubuntu .\Ubuntu\ext4.vhdx
&lt;/code&gt;&lt;/pre&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-hint" data-callout-type="hint"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-fire"&gt;&lt;/i&gt;
Hint
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
This is a faster version credited to Slawek. The old version has been moved to the chapter &lt;a href="https://blog.iany.me/#alternative-step-2-for-wsl1"&gt;Alternative Step 2 for WSL1&lt;/a&gt;.
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;The commands above also unregister the default instance.&lt;/p&gt;
&lt;p&gt;Command explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wsl --export Ubuntu .\Ubuntu\ext4.vhdx --vhd&lt;/code&gt;: Export the disk of the WSL instance with name &lt;code&gt;Ubuntu&lt;/code&gt; into the file &lt;code&gt;ext4.vhdx&lt;/code&gt; in the directory &lt;code&gt;.\Ubuntu\&lt;/code&gt;. So you will get the file &lt;code&gt;D:\WSL\Ubuntu\ext4.vhdx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wsl --unregister Ubuntu&lt;/code&gt;: Unregister the WSL instance with name &lt;code&gt;Ubuntu&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wsl --import-in-place Ubuntu .\Ubuntu\ext4.vhdx&lt;/code&gt;: The first &lt;code&gt;Ubuntu&lt;/code&gt; is the new created instance name. This command will register a new instance using the disk file &lt;code&gt;.\Ubuntu\ext4.vhdx&lt;/code&gt; in place. Keep in mind that if the disk file is deleted, the instance will crash and all the files within it will be permanently lost.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now it is also OK to uninstall Ubuntu in the store.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;. Set the default user for the moved Ubuntu.&lt;/p&gt;
&lt;p&gt;In Step 1, you have created a user for Ubuntu. After export and import, the new instance will use root by default. If you want to continue to use that user, please configure it via registry table.&lt;/p&gt;
&lt;p&gt;Find the directory in registry &lt;code&gt;HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss&lt;/code&gt; which &lt;code&gt;DistributionName&lt;/code&gt; is &amp;ldquo;Ubuntu&amp;rdquo;. Set its &lt;code&gt;DefaultUid&lt;/code&gt; to decimal 1000 (or hex 3e8).&lt;/p&gt;
&lt;p&gt;Or create a file &lt;code&gt;/etc/wsl.conf&lt;/code&gt; in the WSL instance with following contents:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ini"&gt;[user]
default = yournamehere
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;. Migrate to WSL2&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;wsl -l -v&lt;/code&gt; to check whether the new created instance use WSL2. The command &lt;code&gt;wsl --set-version Ubuntu 2&lt;/code&gt; upgrades an instance with name &lt;code&gt;Ubuntu&lt;/code&gt; to version 2.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;. Try wsl&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wsl -d Ubuntu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An alternative solution is using the tool &lt;a href="https://kb.iany.me/para/lets/w/Windows/LxRunOffline"&gt;LxRunOffline&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="alternative-step-2-for-wsl1"&gt;Alternative Step 2 for WSL1&lt;/h2&gt;
&lt;details open disabled class="kg-card kg-callout kg-callout-info" data-callout-type="info"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-circle-info"&gt;&lt;/i&gt;
Info
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
This is the old version of step 2, which is slower but also works for WSL1.
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;. Export the instance and import into the target directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd D:\
mkdir WSL
cd WSL
wsl --export Ubuntu ubuntu.tar
wsl --unregister Ubuntu
mkdir Ubuntu
wsl --import Ubuntu Ubuntu ubuntu.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The commands above also unregister the default instance.&lt;/p&gt;
&lt;p&gt;Command explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wsl --export Ubuntu ubuntu.tar&lt;/code&gt;: Export the WSL instance with name &lt;code&gt;Ubuntu&lt;/code&gt; into the file &lt;code&gt;ubuntu.tar&lt;/code&gt; in the current directory. So you will get the file &lt;code&gt;D:\WSL\ubuntu.tar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wsl --unregister Ubuntu&lt;/code&gt;: Unregister the WSL instance with name &lt;code&gt;Ubuntu&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wsl --import Ubuntu Ubuntu ubuntu.tar&lt;/code&gt;: The first &lt;code&gt;Ubuntu&lt;/code&gt; is the new created instance name. The second &lt;code&gt;Ubuntu&lt;/code&gt; is the instance saved location. The last parameter is the file created by &lt;code&gt;wsl --export&lt;/code&gt;. This will import &lt;code&gt;ubuntu.tar&lt;/code&gt; and use &lt;code&gt;D:\WSL\Ubuntu&lt;/code&gt; to save the WSL instance data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now it is also OK to uninstall Ubuntu in the store and continue to the &lt;strong&gt;Step 3&lt;/strong&gt;.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/windows/">Windows</category><category domain="https://blog.iany.me/tags/wsl/">WSL</category></item><item><title>Podman on Windows via WSL2</title><link>https://blog.iany.me/2020/06/podman-on-windows-via-wsl2/</link><pubDate>Tue, 23 Jun 2020 16:43:25 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/06/podman-on-windows-via-wsl2/</guid><description>&lt;p&gt;I prefer using a dedicated WSL instance to run containers. So I&amp;rsquo;ll install a minimal distribution, Alpine, to run podman.&lt;/p&gt;
&lt;p&gt;Install the tool &lt;code&gt;LxRunOffline&lt;/code&gt; first via scoop&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scoop bucket add extras
scoop install lxrunoffline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Download Alpine root package from &lt;code&gt;https://lxrunoffline.apphb.com/download/Alpine&lt;/code&gt;. See more distributions in &lt;a href="https://github.com/DDoSolitary/LxRunOffline/wiki"&gt;LxRunOffline Wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then create a WSL instance from the package&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lxrunoffline.exe i -n Alpine -f alpine-minirootfs-3.12.0-x86_64.tar.gz -d &amp;quot;D:\WSL\Alpine&amp;quot; -v 2 -r .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install Alpine into folder &lt;code&gt;D:\WSL\Alpine&lt;/code&gt;. Although &lt;code&gt;-v 2&lt;/code&gt; is specified, but when I&amp;rsquo;m running it, the installed instance is still v1. It&amp;rsquo;s easy to fix by migrating to version 2.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wsl --set-version Alpine 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now start the instance&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wsl ~ -d Alpine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/apk/repositories&lt;/code&gt; to switch to edge and add testing repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://dl-cdn.alpinelinux.org/alpine/edge/main
http://dl-cdn.alpinelinux.org/alpine/edge/community
http://dl-cdn.alpinelinux.org/alpine/edge/testing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install &lt;code&gt;podman&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk update
apk add podman
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&amp;rsquo;s time to run Alpine inside Alpine&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;podman run --rm -it docker.io/alpine ash
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/container/">Container</category><category domain="https://blog.iany.me/tags/windows/">Windows</category><category domain="https://blog.iany.me/tags/wsl/">WSL</category></item><item><title>Minimize any window into system tray in Windows</title><link>https://blog.iany.me/2020/06/minimize-any-window-into-system-tray-in-windows/</link><pubDate>Sat, 20 Jun 2020 16:39:57 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/06/minimize-any-window-into-system-tray-in-windows/</guid><description>&lt;p&gt;There are two apps both work for me in scoop extras bucket.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scoop bucket add extras
scoop install rbtray traymond
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They both require running their background process first. Rbtray minimizes a window into system tray by right clicking the window minimization button, while traymond us shortcuts Win + Shift + Z。&lt;/p&gt;
&lt;p&gt;Rbtray has no its own system tray icon. Minimize a window and right click it to quit rbtray. Traymond has its own tray icon, use it to exit or restore all windows.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category><category domain="https://blog.iany.me/tags/tool/">Tool</category><category domain="https://blog.iany.me/tags/windows/">Windows</category></item><item><title>Vim Setup for Windows</title><link>https://blog.iany.me/2020/05/vim-setup-for-windows/</link><pubDate>Tue, 05 May 2020 10:28:26 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/05/vim-setup-for-windows/</guid><description>&lt;p&gt;I used to use Visual Studio Code in Windows as mentioned in a previous post, &lt;a href="https://blog.iany.me/2020/05/my-windows-environment-setup/"&gt;♯ My Windows Environment Setup&lt;/a&gt;. But its startup time is terrible on Surface Go, so I decide to give vim another try.&lt;/p&gt;
&lt;p&gt;First install vim via scoop&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scoop install vim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The most important difference of the Windows vim is that it uses different paths:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.vimrc -&amp;gt; _vimrc
.gvimrc -&amp;gt; _gvimrc
.vim -&amp;gt; vimfiles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example &lt;a href="https://github.com/junegunn/vim-plug"&gt;plug.vim&lt;/a&gt; has to be saved as &lt;code&gt;~/vimfiles/autoload/plug.vim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Following are some Windows specific options:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-vim"&gt;&amp;quot; Force color and encoding. Put these near the top of the config file.
if has(&amp;quot;win32&amp;quot;)
set t_Co=256
set encoding=utf-8
endif
if has(&amp;quot;win32&amp;quot;)
&amp;quot; Support different cursor shapes in Windows Terminal
let &amp;amp;t_SI=&amp;quot;\&amp;lt;CSI&amp;gt;5 q&amp;quot;
let &amp;amp;t_EI=&amp;quot;\&amp;lt;CSI&amp;gt;1 q&amp;quot;
&amp;quot; Disable fzf preview because it is broken in PowerShell
let g:fzf_preview_window = ''
&amp;quot; Use PowerShell as the shell
set shell=powershell.exe
set shellcmdflag=-NoLogo\ -NoProfile\ -NonInteractive\ -command
endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And options in &lt;code&gt;_gvimrc&lt;/code&gt; for gvim:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-vim"&gt;set guifont=JetBrains_Mono:h11
set guioptions-=m
&amp;quot; Show ligatures. It is not perfect and requires C-L to manually refresh.
set renderoptions=type:directx
&amp;quot; Fix the ugly cursor color
hi Cursor guibg=#005f87 guifg=#eeeeee
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/vim/">Vim</category><category domain="https://blog.iany.me/tags/windows/">Windows</category></item><item><title>Pass, A Password Manager Utilizing GPG and Git</title><link>https://blog.iany.me/2020/05/pass-a-password-manager-utilizing-gpg-and-git/</link><pubDate>Sun, 03 May 2020 22:50:53 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/05/pass-a-password-manager-utilizing-gpg-and-git/</guid><description>&lt;p&gt;A friend recommended &lt;a href="https://github.com/gopasspw/gopass"&gt;gopass&lt;/a&gt; to manage passwords. After the trial, I decided to switch.&lt;/p&gt;
&lt;p&gt;Gopass is indeed an implementation which follows the protocols defined by &lt;a href="https://www.passwordstore.org/"&gt;pass&lt;/a&gt;. It utilizes gpg to encrypt files and git to synchronize. It is really fast to manage the password vault because I already used to the command line environment and text editing tools.&lt;/p&gt;
&lt;p&gt;I also use &lt;a href="https://github.com/browserpass/browserpass-extension"&gt;browserpass&lt;/a&gt; in Chrome and &amp;ldquo;Pass - Password Manager&amp;rdquo; in iOS.&lt;/p&gt;
&lt;p&gt;I migrated from Enpass. There is a tool &lt;a href="https://github.com/roddhjav/pass-import"&gt;pass-import&lt;/a&gt;, but it does not work well on my exported Enpass vault, so I decide to build the pass store from scratch. Gopass indeed can encrypt and save any file, thus I just add the whole exported Enpass CSV file into the repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gopass edit enpass.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy and paste the exported Enpass CSV file into the opened editor and save. It is easy to search in the file using a pager or grep.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gopass show enpass.csv | less
gopass show enpass.csv | grep google
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can rebuild the password database with a create-on-use strategy: Whenever I want to use a password which has no its own entry yet, I search it in &lt;code&gt;enpass.csv&lt;/code&gt; and create one.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/git/">Git</category><category domain="https://blog.iany.me/tags/gpg/">GPG</category><category domain="https://blog.iany.me/tags/password-manager/">Password Manager</category><category domain="https://blog.iany.me/tags/security/">Security</category></item><item><title>My Windows Environment Setup</title><link>https://blog.iany.me/2020/05/my-windows-environment-setup/</link><pubDate>Sun, 03 May 2020 22:03:22 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/05/my-windows-environment-setup/</guid><description>&lt;p&gt;I have only one Windows device, the Surface Go. I work on it occasionally, especially on short trips. I prefer Surface Go because of handwriting. I have a simple setup to meet my work requirements.&lt;/p&gt;
&lt;p&gt;I spend the most time on a computer in three apps: terminal, text editor and web browser. I use Windows Terminal, Visual Studio Code and Chrome in Windows.&lt;/p&gt;
&lt;p&gt;I use &lt;a href="https://github.com/microsoft/terminal/tree/master/src/tools/ColorTool"&gt;ColorTool&lt;/a&gt; to convert my favorite theme &lt;a href="https://github.com/aseom/dotfiles/blob/master/osx/iterm2/papercolor-light.itermcolors"&gt;PaperColor&lt;/a&gt; into Windows Terminal schema. &lt;a href="https://gist.github.com/doitian/4677ce2da2eca2eccbb1637ef804bed1"&gt;This&lt;/a&gt; is my full Windows Terminal config file.&lt;/p&gt;
&lt;p&gt;Since I don&amp;rsquo;t do heavy development locally, I use PowerShell as default in the Windows Terminal. The command &lt;code&gt;echo $profile&lt;/code&gt; will print the PowerShell config file path. The essential PowerShell config is enabling the Emacs style keybinding (full version &lt;a href="https://gist.github.com/doitian/db79d2dbfaa24093534c7411b0a926bd"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-PSReadLineOption -EditMode emacs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://scoop.sh/"&gt;Scoop&lt;/a&gt; is very handy to install essential command line utilities, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scoop install mingit ripgrep
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use the OpenSSH Client component in Windows, which can be enabled in &amp;ldquo;Settings / Apps / Manage optional features&amp;rdquo;. I used to set an SSH passphrase, but the ssh agent service is disabled. It can be enabled in PowerShell ran as admin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Run once as admin
Get-Service ssh-agent | Set-Service -StartupType manual
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have set the startup type to manual, so if I want to load the key, I have to start the service first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Start-Service ssh-agent
ssh-add
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, the git installed by scoop uses its own bundled ssh client by default. It is easy to fix it by setting an environment variable &lt;code&gt;GIT_SSH&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Run once as logged in user
$SSHPath = (Get-Command -Name 'ssh.exe').Source
[Environment]::SetEnvironmentVariable('GIT_SSH', $SSHPath, 'User')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Windows Terminal and now git will use the key in ssh agent.&lt;/p&gt;
&lt;p&gt;Git has the same issue with GPG. Use &lt;code&gt;git config&lt;/code&gt; to tell where the GPG program is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global gpg.program (Get-Command -Name 'gpg.exe').Source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Following is a list of other apps I used in Windows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XMind: Mindmap tool.&lt;/li&gt;
&lt;li&gt;Drawboard PDF: PDF reading and annotation.&lt;/li&gt;
&lt;li&gt;Leonardo: an infinite canvas painting app.&lt;/li&gt;
&lt;li&gt;Nebo: Handwriting notebook.&lt;/li&gt;
&lt;li&gt;Clash for Windows&lt;/li&gt;
&lt;li&gt;OneDrive and Google Keep for quickly synchronize data with other devices.&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/dev-environment/">Dev Environment</category><category domain="https://blog.iany.me/tags/windows/">Windows</category></item><item><title>Write to Any iOS Files Location via Working Copy</title><link>https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/</link><pubDate>Fri, 24 Apr 2020 21:14:17 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/</guid><description>&lt;p&gt;I use file system to manage my knowledge base. The &lt;a href="https://github.com/doitian/knowledge-base/"&gt;repository&lt;/a&gt; has a well defined directory structure. I have a bunch of scripts to help me creating these directories on the laptop. But in iOS, I have to create them manually. Recently, I have found out a way to work around it via Working Copy.&lt;/p&gt;
&lt;p&gt;The trick uses two features of Working Copy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2-way synchronized repository with any iOS Files location.&lt;/li&gt;
&lt;li&gt;The shortcut &lt;em&gt;Write Repository File&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The shortcut allows writing file into any path in a repository, and the synchronized repository synchronizes changes back to the target iOS Files location.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Write to Files via Working Copy" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/write-to-files-via-working-copy_hu_9afd16b0ae602838.png" srcset="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/write-to-files-via-working-copy_hu_512138b46b9eaad5.png 400w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/write-to-files-via-working-copy_hu_aa3d7fbfb4ac5208.png 800w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/write-to-files-via-working-copy_hu_9e7916f9cf969714.png 1200w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/write-to-files-via-working-copy_hu_9afd16b0ae602838.png 1477w" sizes="(max-width: 1200px) 100vw, 1477px" /&gt;
&lt;figcaption &gt;Write to Files via Working Copy&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In the following I&amp;rsquo;ll show how to create a shortcut which saves files into an iCloud folder and sorts them into sub-directories by date.&lt;/p&gt;
&lt;p&gt;First create the target directory and set up the synchronized repository. I use &lt;code&gt;Desktop/Inbox&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Set Up Sync Repo in Working Copy" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/set-up-sync-repo-in-working-copy_hu_3095b87da92af2f4.jpg" srcset="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/set-up-sync-repo-in-working-copy_hu_7fda3cc432e7063a.jpg 400w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/set-up-sync-repo-in-working-copy_hu_62d8fe3cb98c4823.jpg 800w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/set-up-sync-repo-in-working-copy_hu_c40ba0adc1c4614d.jpg 1200w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/set-up-sync-repo-in-working-copy_hu_329a07dcf085759f.jpg 1600w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/set-up-sync-repo-in-working-copy_hu_3095b87da92af2f4.jpg 1960w" sizes="(max-width: 1600px) 100vw, 1960px" /&gt;
&lt;figcaption &gt;Set Up Sync Repo in Working Copy&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Then create the shortcut. It accepts the file from Share Sheet and saves it into the synchronized repository sorted by date. It is important to check the option &amp;ldquo;Create&amp;rdquo; in the &lt;em&gt;Write Repository File&lt;/em&gt; step.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Shortcut: Save Files by Date" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/shortcut-save-files-by-date_hu_2ef68996f341c0e7.jpg" srcset="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/shortcut-save-files-by-date_hu_c3b8177ce33cd5b1.jpg 400w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/shortcut-save-files-by-date_hu_eb865d4b1f65914b.jpg 800w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/shortcut-save-files-by-date_hu_7e54a7293804a5ba.jpg 1200w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/shortcut-save-files-by-date_hu_2ef68996f341c0e7.jpg 1325w" sizes="(max-width: 1200px) 100vw, 1325px" /&gt;
&lt;figcaption &gt;Shortcut: Save Files by Date&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now give it a try. Share a text file using the new created action.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Test Shortcut" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/test-shortcut_hu_e7e1b81611d53ee3.jpg" srcset="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/test-shortcut_hu_892594feb1e07da4.jpg 400w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/test-shortcut_hu_9d797af8afb2c487.jpg 800w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/test-shortcut_hu_d9be8049f655237.jpg 1200w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/test-shortcut_hu_7fc93bbd89f89200.jpg 1600w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/test-shortcut_hu_e7e1b81611d53ee3.jpg 1942w" sizes="(max-width: 1600px) 100vw, 1942px" /&gt;
&lt;figcaption &gt;Test Shortcut&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;And the file appears in the &lt;code&gt;Desktop/Inbox&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="File Sorted into Inbox" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/file-sorted-into-inbox_hu_d9683c4cd4404ec9.jpg" srcset="https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/file-sorted-into-inbox_hu_5005298fae4abcd1.jpg 400w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/file-sorted-into-inbox_hu_e72e0678ea3e753a.jpg 800w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/file-sorted-into-inbox_hu_6ff455e89458946.jpg 1200w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/file-sorted-into-inbox_hu_17c8f9927956ea78.jpg 1600w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/file-sorted-into-inbox_hu_16d6ed64435dc66.jpg 2000w, https://blog.iany.me/2020/04/write-to-any-ios-files-location-via-working-copy/file-sorted-into-inbox_hu_d9683c4cd4404ec9.jpg 2048w" sizes="(max-width: 2000px) 100vw, 2048px" /&gt;
&lt;figcaption &gt;File Sorted into Inbox&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;This solution is not perfect and has two major drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It duplicates the files.&lt;/li&gt;
&lt;li&gt;It requires opening Working Copy to synchronize files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, it also has a bonus. I can backup the directory by git now.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/ios/">iOS</category></item><item><title>Rust Pin</title><link>https://blog.iany.me/2020/04/rust-pin/</link><pubDate>Sat, 11 Apr 2020 21:08:33 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/04/rust-pin/</guid><description>&lt;p&gt;&lt;code&gt;Pin&lt;/code&gt; is an obscure type in Rust because of the naming and indirect concepts.&lt;/p&gt;
&lt;p&gt;The first indirect concept is pointer. &lt;code&gt;Pin&amp;lt;X&amp;gt;&lt;/code&gt; does not guarantee that X will not move. If X is a pointer which target type is T, &lt;code&gt;Pin&amp;lt;X&amp;gt;&lt;/code&gt; guarantees that T will not move.&lt;/p&gt;
&lt;p&gt;The second is &amp;ldquo;not move&amp;rdquo;. It really means that the only way to get the mut reference to T is via unsafe interface.&lt;/p&gt;
&lt;p&gt;The last is the &lt;code&gt;Unpin&lt;/code&gt; mark trait. Pin forbids safe interface to get the mut reference to T only when T is &lt;code&gt;!Unpin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In simple words, Pin is a pointer wrapper. When a pointer is trapped inside Pin, and the pointee type is &lt;code&gt;!Unpin&lt;/code&gt;, there&amp;rsquo;s no safe way to get a mut reference to the pointee.&lt;/p&gt;
&lt;p&gt;The following diagram has listed what Pin provides and constraints on when we can use those functions.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Rust Pin" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/04/rust-pin/Rust%20Pin_hu_223e55d1744a8de7.png" srcset="https://blog.iany.me/2020/04/rust-pin/Rust%20Pin_hu_9a49407eb0b9f490.png 400w, https://blog.iany.me/2020/04/rust-pin/Rust%20Pin_hu_40a792bdf744ce0d.png 800w, https://blog.iany.me/2020/04/rust-pin/Rust%20Pin_hu_44d2a159ada1f2a1.png 1200w, https://blog.iany.me/2020/04/rust-pin/Rust%20Pin_hu_b2a20cac56bf1a4b.png 1600w, https://blog.iany.me/2020/04/rust-pin/Rust%20Pin_hu_223e55d1744a8de7.png 1800w" sizes="(max-width: 1600px) 100vw, 1800px" /&gt;
&lt;/figure&gt;
&lt;p&gt;In Rust, the pointer is indeed the trait &lt;code&gt;Deref&lt;/code&gt; and &lt;code&gt;DerefMut&lt;/code&gt;, from which we can get the shared or mut reference.&lt;/p&gt;
&lt;p&gt;It is not very interesting when the pointer is &lt;code&gt;Deref&lt;/code&gt;. The essential of Pin is when X is &lt;code&gt;DerefMut&lt;/code&gt;. The yellow box shows that the safe interface to get the mut reference is available when T is &lt;code&gt;Unpin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Unpin&lt;/code&gt; is implemented for types by default. It acts as a safety which disables the core feature of Pin. Pin is only effective when the safety is turned off, a.k.a, when the type is explicitly marked as &lt;code&gt;!Unpin&lt;/code&gt; via &lt;a href="https://doc.rust-lang.org/std/marker/struct.PhantomPinned.html"&gt;PhantomPinned&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pin is a signal that a self reference type may appear here. It is also a contract. The type provider must mark the type as &lt;code&gt;!Unpin&lt;/code&gt; if it is unsafe to move. The type user must promise not to move the &lt;code&gt;!Unpin&lt;/code&gt; data in the unsafe block.&lt;/p&gt;
&lt;h2 id="further-readings"&gt;Further Readings&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://doc.rust-lang.org/std/pin/index.html"&gt;pin module&lt;/a&gt; document has explained why and the typical scenario.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is sometimes useful to have objects that are guaranteed not to move, in the sense that their placement in memory does not change, and can thus be relied upon. A prime example of such a scenario would be building self-referential structs, as moving an object with pointers to itself will invalidate them, which could cause undefined behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Pin&lt;/code&gt; was suggested in &lt;a href="https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md"&gt;RFC#2349&lt;/a&gt;. It was also well explained in the book &lt;a href="https://cfsamson.github.io/books-futures-explained/5_pin.html"&gt;Futures Explained in 200 Lines of Rust.&lt;/a&gt;&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/async-programming/">Async Programming</category><category domain="https://blog.iany.me/tags/rust/">Rust</category></item><item><title>Fix iOS App Store Family Purchase Sharing</title><link>https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/</link><pubDate>Sun, 22 Mar 2020 19:01:28 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/</guid><description>&lt;p&gt;I have created a family group using the Apple ID &lt;em&gt;A&lt;/em&gt;, and the account &lt;em&gt;B&lt;/em&gt; is a member. However, I can&amp;rsquo;t download apps purchased by &lt;em&gt;A&lt;/em&gt; in the device logged in by &lt;em&gt;B&lt;/em&gt;. Today, I finally find out the cause. The Purchase Sharing setting in account &lt;em&gt;B&lt;/em&gt; is incorrect, which is by accident set to share as &lt;em&gt;A&lt;/em&gt; and I don&amp;rsquo;t know why.&lt;/p&gt;
&lt;p&gt;To fix it, just ensure every family member has set to share purchase as themselves in the Purchase Sharing setting.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Ensure Correct Purchase Sharing Settings in the Family Member Accounts" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/ensure-correct-purchase-sharing-settings-squashed_hu_36d7ccfabe437af6.png" srcset="https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/ensure-correct-purchase-sharing-settings-squashed_hu_bb4117e7c9509f98.png 400w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/ensure-correct-purchase-sharing-settings-squashed_hu_808f574e76caf53.png 800w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/ensure-correct-purchase-sharing-settings-squashed_hu_d80e840e8e0ea2a1.png 1200w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/ensure-correct-purchase-sharing-settings-squashed_hu_36d7ccfabe437af6.png 1290w" sizes="(max-width: 1200px) 100vw, 1290px" /&gt;
&lt;figcaption &gt;Ensure Correct Purchase Sharing Settings in the Family Member Accounts&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In case you don&amp;rsquo;t know how to download the Family Shared Purchase:&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Download Family Purchase Sharing" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/download-ios-app-store-family-purchase-sharings-squashed_hu_327cc9f4a57a47e4.png" srcset="https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/download-ios-app-store-family-purchase-sharings-squashed_hu_f7164a88c2254dcc.png 400w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/download-ios-app-store-family-purchase-sharings-squashed_hu_96f1965b7b5b4324.png 800w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/download-ios-app-store-family-purchase-sharings-squashed_hu_3d9084712637db7e.png 1200w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/download-ios-app-store-family-purchase-sharings-squashed_hu_f2406286037f7de7.png 1600w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/download-ios-app-store-family-purchase-sharings-squashed_hu_92b2e6232d36408b.png 2000w, https://blog.iany.me/2020/03/fix-ios-app-store-family-purchase-sharing/download-ios-app-store-family-purchase-sharings-squashed_hu_327cc9f4a57a47e4.png 2048w" sizes="(max-width: 2000px) 100vw, 2048px" /&gt;
&lt;figcaption &gt;Download Family Purchase Sharing&lt;/figcaption&gt;
&lt;/figure&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/app-store/">App Store</category><category domain="https://blog.iany.me/tags/ios/">iOS</category></item><item><title>Save Web Articles via Inoreader</title><link>https://blog.iany.me/2020/03/save-web-articles-via-inoreader/</link><pubDate>Sat, 14 Mar 2020 17:02:25 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/03/save-web-articles-via-inoreader/</guid><description>&lt;p&gt;One of my favorite features of &lt;a href="https://www.inoreader.com/"&gt;Inoreader&lt;/a&gt; is saving the article to external services, such as Evernote, OneDrive or Google Drive. I can archive the articles I liked for future references.&lt;/p&gt;
&lt;p&gt;Inoreader even supports automatically fetching full text for truncated RSS feed. This is awesome to work with other web services. Many bookmarking and reading later services, such as Diigo, Instapaper and Pinboard, all provide RSS feed but without full text.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Automatically Fetch Full Content" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-fetches-full-text-squashed_hu_3f89695cf348133b.png" srcset="https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-fetches-full-text-squashed_hu_9dedc0443ec44659.png 400w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-fetches-full-text-squashed_hu_ed595609dd430cf9.png 800w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-fetches-full-text-squashed_hu_3f89695cf348133b.png 997w" sizes="(max-width: 800px) 100vw, 997px" /&gt;
&lt;figcaption &gt;Automatically Fetch Full Content&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Inoreader Pro subscribers can save the articles automatically by creating rules. For example, I have saved all of my bookmarks into Evernote.&lt;/p&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Automatically Save Articles" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-rule-saves-article-squashed_hu_39a9a697ecfc5d81.png" srcset="https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-rule-saves-article-squashed_hu_9479f887cf05c4a2.png 400w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-rule-saves-article-squashed_hu_ea67e9330e9165ac.png 800w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-rule-saves-article-squashed_hu_b2bb9b766243b037.png 1200w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/inoreader-rule-saves-article-squashed_hu_39a9a697ecfc5d81.png 1494w" sizes="(max-width: 1200px) 100vw, 1494px" /&gt;
&lt;figcaption &gt;Automatically Save Articles&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="kg-image-card kg-width-full"&gt;
&lt;img alt="Saved Articles" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/03/save-web-articles-via-inoreader/evernote-saved-articles-squashed_hu_e1e9f118b7878c6e.png" srcset="https://blog.iany.me/2020/03/save-web-articles-via-inoreader/evernote-saved-articles-squashed_hu_abf7a4c03404ec7.png 400w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/evernote-saved-articles-squashed_hu_b897d6ddfc944b14.png 800w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/evernote-saved-articles-squashed_hu_d1a31eb25b156e5f.png 1200w, https://blog.iany.me/2020/03/save-web-articles-via-inoreader/evernote-saved-articles-squashed_hu_e1e9f118b7878c6e.png 1206w" sizes="(max-width: 1200px) 100vw, 1206px" /&gt;
&lt;figcaption &gt;Saved Articles&lt;/figcaption&gt;
&lt;/figure&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/knowledge-management/">Knowledge Management</category></item><item><title>Blocking Stdout</title><link>https://blog.iany.me/2020/03/blocking-stdout/</link><pubDate>Sun, 01 Mar 2020 09:29:40 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/03/blocking-stdout/</guid><description>&lt;p&gt;When I first read Stjepan&amp;rsquo;s article &lt;a href="https://web.archive.org/web/20200815123809/https://stjepang.github.io/2019/12/04/blocking-inside-async-code.html"&gt;Blocking inside async code&lt;/a&gt;, I never though I will met the problem mentioned in the post.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;… I bet we all most of the time assume printing to standard output does not block while it really could.&lt;/p&gt;
&lt;p&gt;In case you’re wondering why &lt;code&gt;println!()&lt;/code&gt; can block, imagine we executed &lt;code&gt;program1 | program2&lt;/code&gt; in a shell so that the output of &lt;code&gt;program1&lt;/code&gt; is piped into &lt;code&gt;program2&lt;/code&gt;. If &lt;code&gt;program2&lt;/code&gt; is reading input very slowly, then &lt;code&gt;program1&lt;/code&gt; will have to block whenever it prints something and the pipe is full.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fortunately, my brain has stored the clue somewhere, and I can retrieve it and save my day when I heart a weird bug.&lt;/p&gt;
&lt;p&gt;We have a GUI app &lt;strong&gt;N&lt;/strong&gt; written in Node which bundles a service binary &lt;strong&gt;C&lt;/strong&gt;. &lt;strong&gt;N&lt;/strong&gt; starts &lt;strong&gt;C&lt;/strong&gt; as a sub-process. &lt;strong&gt;C&lt;/strong&gt; writes logs to a file. After 10 minutes, it stops writing the log file until restarted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; uses a single thread to write logs. But default, it sends the logs to both stdout and log file. When the stdout buffer of &lt;strong&gt;C&lt;/strong&gt; is full, the logging thread stuck, so it also stops writing the log file.&lt;/p&gt;
&lt;p&gt;A simple fix is telling &lt;strong&gt;C&lt;/strong&gt; not write to stdout. But the root cause is that the app &lt;strong&gt;N&lt;/strong&gt; keeps the child process stdout pipe open and never read from the pipe. The best practice is that if you don&amp;rsquo;t read from the pipe, close it.&lt;/p&gt;
&lt;p&gt;Following is an example In Node to close both stdin and stdout via &lt;a href="https://nodejs.org/api/child_process.html#child_process_options_stdio"&gt;option stdio&lt;/a&gt;. The stderr is open because the parent process will read from the pipe.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;const c = spawn('c', [], {
stdio: ['ignore', 'ignore', 'pipe']
});
c.stderr.on('data', (data) =&amp;gt; {
console.log(`c: ${data}`);
});
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/async-programming/">Async Programming</category><category domain="https://blog.iany.me/tags/nodejs/">Node.js</category><category domain="https://blog.iany.me/tags/rust/">Rust</category></item><item><title>Excel as Diagram Maker</title><link>https://blog.iany.me/2020/02/excel-as-diagram-maker/</link><pubDate>Sat, 22 Feb 2020 21:18:00 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/02/excel-as-diagram-maker/</guid><description>&lt;p&gt;Excel is a full featured vector graphics app. It has bundled many shapes and styles, and even supports anchoring connectors to the shapes. All these features make Excel also a good diagram maker.&lt;/p&gt;
&lt;p&gt;One of my favorite tips is grid snapping, it can save a lot of time to align shapes. I also recommend resizing the grid as small squares.&lt;/p&gt;
&lt;p&gt;I have recorded a screencast for it. The video has demonstrated:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Resize the grid into small squares.&lt;/li&gt;
&lt;li&gt;Insert a shape to open the &lt;em&gt;Shape Format&lt;/em&gt; menu.&lt;/li&gt;
&lt;li&gt;Enable snapping features in the &lt;em&gt;Align&lt;/em&gt; menu.&lt;/li&gt;
&lt;li&gt;Insert more shapes and add connectors.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="kg-card kg-embed-card"&gt;
&lt;iframe src="https://player.vimeo.com/video/392445371" width="640" height="391" frameborder="0" allow="autoplay; fullscreen" allowfullscreen&gt;&lt;/iframe&gt;
&lt;figcaption&gt;Draw Diagrams in Excel&lt;/figcaption&gt;
&lt;/figure&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/power-tool/">Power Tool</category><category domain="https://blog.iany.me/tags/screencast/">Screencast</category></item><item><title>Bitcoin Core Network Event Loops</title><link>https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/</link><pubDate>Wed, 12 Feb 2020 20:22:00 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/</guid><description>&lt;p&gt;This article is an analysis of the network event loops based on bitcoin core &lt;a href="https://github.com/bitcoin/bitcoin/tree/v0.19.0"&gt;v0.19.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bitcoin &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L2211"&gt;starts two threads&lt;/a&gt; to handle network messages, and each thread runs its own event loop.&lt;/p&gt;
&lt;h2 id="socket-handler-thread"&gt;Socket Handler Thread&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1282"&gt;Socket Handler Thread&lt;/a&gt; is responsible for the underlying socket IO.&lt;/p&gt;
&lt;p&gt;It loops each connected peer in a round-robin schedule. The handler reads messages from the peer socket into the receiving queue &lt;code&gt;vProcessMsg&lt;/code&gt;, and sends message in the sending queue &lt;code&gt;vSendMsg&lt;/code&gt; to peer.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Socket Handler" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/socket-handler_hu_ad947e33992617d9.png" srcset="https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/socket-handler_hu_1d0c60f777510f7f.png 400w, https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/socket-handler_hu_ad947e33992617d9.png 714w" sizes="(max-width: 400px) 100vw, 714px" /&gt;
&lt;figcaption &gt;Socket Handler&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="read-from-socket"&gt;Read from Socket&lt;/h3&gt;
&lt;p&gt;First, the thread tries to read some data from current peer.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1338"&gt;Read some data from socket&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1343"&gt;Decode the message&lt;/a&gt;. The newly decoded messages are stored in a queue named &lt;code&gt;vRecvMsg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1356"&gt;Move&lt;/a&gt; the decoded message in &lt;code&gt;vRecvMsg&lt;/code&gt; to another queue &lt;code&gt;vProcessMsg&lt;/code&gt;. The queue &lt;code&gt;vProcessMsg&lt;/code&gt; is the inbox for the Message Handler Thread.&lt;/li&gt;
&lt;li&gt;If there is any new decoded message in this loop step, try to &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1360"&gt;wake&lt;/a&gt; the Message Handler Thread.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="write-to-socket"&gt;Write to Socket&lt;/h3&gt;
&lt;p&gt;Then, the thread &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1390"&gt;sends&lt;/a&gt; queued messages in &lt;code&gt;vSendMsg&lt;/code&gt; to current peer.&lt;/p&gt;
&lt;h3 id="code-references"&gt;Code References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L2211"&gt;&lt;code&gt;CConnman::Start&lt;/code&gt;&lt;/a&gt;: This is the main entry to start network threads.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1282"&gt;&lt;code&gt;CConnman::SocketHandler&lt;/code&gt;&lt;/a&gt;: The main logic of Socket Handler Thread.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L565"&gt;&lt;code&gt;CNode::ReceiveMsgBytes&lt;/code&gt;&lt;/a&gt;: Decode received bytes into message.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="message-handler-thread"&gt;Message Handler Thread&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1978"&gt;Message Handler Thread&lt;/a&gt; handles the network messages without worrying the socket operations.&lt;/p&gt;
&lt;p&gt;The behavior of the Message Handler Thread is similar to the Socket Handler Thread. Both loops each connected peer in the round-robin way. And for each peer, they first process incoming messages, and then send outgoing messages. The difference is that Message Handler Thread reads messages from the queue &lt;code&gt;vProcessMsg&lt;/code&gt; and queues outgoing messages in the queue &lt;code&gt;vSendMsg&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Message Handler" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/message-handler_hu_aab368e4074ba9ee.png" srcset="https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/message-handler_hu_e157d452fe49e7f8.png 400w, https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/message-handler_hu_aab368e4074ba9ee.png 555w" sizes="(max-width: 400px) 100vw, 555px" /&gt;
&lt;figcaption &gt;Message Handler&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The two loops are connected at these two message queues.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Two Loops are connected on two queues." class="kg-image" loading="lazy" src="https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/two-loops_hu_b98a59edeeb745ca.png" srcset="https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/two-loops_hu_7cbd35db0c288629.png 400w, https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/two-loops_hu_ff780c8c05396a8.png 800w, https://blog.iany.me/2020/02/bitcoin-core-network-event-loops/two-loops_hu_b98a59edeeb745ca.png 1008w" sizes="(max-width: 800px) 100vw, 1008px" /&gt;
&lt;figcaption &gt;Two Loops are connected on two queues.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="process-incoming-messages"&gt;Process Incoming Messages&lt;/h3&gt;
&lt;p&gt;The Message Handler Thread &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1999"&gt;processes the incoming messages&lt;/a&gt; one by one in &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net_processing.cpp#L1862"&gt;the function &lt;code&gt;ProcessMessage&lt;/code&gt; with a huge switch structure&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Each peer acts as a state machine. For some reasons, the state is stored in two different locations.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;CNode&lt;/code&gt; structure, which is passed around as a function parameter, such as &lt;code&gt;pfrom&lt;/code&gt; in &lt;code&gt;ProcessMessage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;CNodeState&lt;/code&gt; stored in a &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net_processing.cpp#L397"&gt;global table&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="schedule-more-outgoing-messages"&gt;Schedule more Outgoing Messages&lt;/h3&gt;
&lt;p&gt;&lt;a name="a17745"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Besides the queued messages acting as responses to the incoming messages, the Message Handler Thread also &lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net_processing.cpp#L3561"&gt;schedules more outgoing messages&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are mainly two kinds of outgoing messages in this step:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Messages depending on timer, either periodic or the follow ups upon time out.&lt;/li&gt;
&lt;li&gt;Messages queued and should be sent in batch, such as blocks and transactions announcements.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="code-references-1"&gt;Code References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net.cpp#L1978"&gt;&lt;code&gt;CConnman::ThreadMessageHandler&lt;/code&gt;&lt;/a&gt;: The Message Handler Thread.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net_processing.cpp#L1862"&gt;&lt;code&gt;::ProcessMessage&lt;/code&gt;&lt;/a&gt;: This function processes a single incoming message.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bitcoin/bitcoin/blob/1bc9988993ee84bc814e5a7f33cc90f670a19f6a/src/net_processing.cpp#L3561"&gt;&lt;code&gt;PeerLogicValidation::SendMessages&lt;/code&gt;&lt;/a&gt;: Check timer and batch queue and schedule more outgoing messages.&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/blockchain/">Blockchain</category><category domain="https://blog.iany.me/tags/programming/">Programming</category></item><item><title>Vim iTerm Launcher</title><link>https://blog.iany.me/2020/02/vim-iterm-launcher/</link><pubDate>Sat, 08 Feb 2020 16:48:30 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/02/vim-iterm-launcher/</guid><description>&lt;p&gt;I prefer using Vim in a terminal. I can switch to the shell with &lt;kbd&gt;Ctrl-Z&lt;/kbd&gt; and back with &lt;code&gt;fg&lt;/code&gt;. However it is hard to integrate a terminal command with other GUI tools, such as editing a file in Vim from Finder.&lt;/p&gt;
&lt;p&gt;In macOS, the default Terminal app and iTerm both supports automation. It&amp;rsquo;s easy to write a script to open a new terminal window and run a command like &amp;ldquo;vim file&amp;rdquo; in it. But I want to close the window after Vim quits. A quick work around is running the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim file; exit 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the problem is that if I suspend Vim via &lt;kbd&gt;Ctrl-Z&lt;/kbd&gt;, the terminal window is closed, because shell will continue to execute the next command when the process is suspended.&lt;/p&gt;
&lt;p&gt;After research and reading &lt;a href="https://stackoverflow.com/a/16215525/667158"&gt;a StackOverflow answer&lt;/a&gt;, I wrote &lt;a href="https://gist.github.com/doitian/0c8775e88ceed7bac44c4fb4287822d5"&gt;two scripts&lt;/a&gt; to launch Vim in iTerm. I also added features in &lt;code&gt;iterm-vim-wrapper&lt;/code&gt; to edit clipboard and empty scratch file in temporary directory.&lt;/p&gt;
&lt;p&gt;This is &lt;a href="https://github.com/doitian/assets/raw/master/2020/Vim%20File%20Action.alfredworkflow"&gt;an example&lt;/a&gt; which uses the scripts to create File Action in Alfred.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/macos/">macOS</category></item><item><title>How to Download Only Gmail Inbox</title><link>https://blog.iany.me/2020/02/download-only-gmail-inbox/</link><pubDate>Sun, 02 Feb 2020 12:15:10 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2020/02/download-only-gmail-inbox/</guid><description>&lt;p&gt;I prefer reading my mails in the Gmail web client directly. I rarely send new mails or replies. But when I do, I want to use PGP to encrypt or sign the mail. I have tried two extensions to use PGP in Gmail, FlowCrypt and Mailvelope. But both of them are far from a competent solution for me. GPG Mail in the GPG Suite for macOS is still my preferred way. However, GPG Mail is an Apple Mail plugin, which requires downloading mails first. Based on my scenarios, I only need to download the mails I left in the inbox. But the basic setup of Gmail in Apple Mail will download many mails in the background.&lt;/p&gt;
&lt;p&gt;I tried POP3. It has two issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The status is not synchronized with the server. I can configure Gmail to archive the mails which have been downloaded via POP3, but that means once I have downloaded mails, I must process them locally. This is a burden, since I only want to reply several mails in the client.&lt;/li&gt;
&lt;li&gt;POP3 does not observe the filter which auto archives new mails. The filtered emails still occur in the client.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IMAP is still the better choice. The real issue is indeed that Apple Mail uses remote IMAP mail boxes for Drafts, Sent, Junk, Trash and Archive by default. If it uses local mail boxes instead, the client will no longer try to synchronize this mail boxes.&lt;/p&gt;
&lt;p&gt;Following is the detailed instructions to setup a Gmail IMAP client which only downloads mails in the inbox.&lt;/p&gt;
&lt;p&gt;First go to the Gmail &lt;a href="https://mail.google.com/mail/u/0/#settings/labels"&gt;label settings&lt;/a&gt;. Disable all the &amp;ldquo;Show in IMAP&amp;rdquo; options except the one for inbox.&lt;/p&gt;
&lt;p&gt;Since Apple Mail does not allow configuring mailboxes for a Google account, I have to generate an &lt;a href="https://myaccount.google.com/apppasswords"&gt;app password&lt;/a&gt; to add the Gmail as an IMAP account. Open Mail app, add account and choose IMAP from &amp;ldquo;Other mail account…&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;After adding the new account, use local mail boxes in the Mailbox Behaviors settings pane. Leave the Archive mail box remote, since we cannot change it.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Apple Mail Mailboxes Behaviors Settings Pane" class="kg-image" loading="lazy" src="https://blog.iany.me/2020/02/download-only-gmail-inbox/apple-mail-mailboxes-behaviors_hu_5004a41c08c53fa3.png" srcset="https://blog.iany.me/2020/02/download-only-gmail-inbox/apple-mail-mailboxes-behaviors_hu_1f0da9588835395a.png 400w, https://blog.iany.me/2020/02/download-only-gmail-inbox/apple-mail-mailboxes-behaviors_hu_5004a41c08c53fa3.png 710w" sizes="(max-width: 400px) 100vw, 710px" /&gt;
&lt;figcaption &gt;Apple Mail Mailboxes Behaviors Settings Pane&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now Apple Mail will only download mails in the inbox. There is one last thing, if I archive a mail in the client, it will be labeled &lt;code&gt;[Imap]/Archive&lt;/code&gt; in Gmail, you may want to hide it from the label list and message like me in the &lt;a href="https://mail.google.com/mail/u/0/#settings/labels"&gt;labels settings&lt;/a&gt;.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/email/">Email</category><category domain="https://blog.iany.me/tags/power-tool/">Power Tool</category></item><item><title>Allocate Energy to Categories</title><link>https://blog.iany.me/2019/08/allocate-energy-to-categories/</link><pubDate>Sun, 18 Aug 2019 12:42:26 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2019/08/allocate-energy-to-categories/</guid><description>&lt;p&gt;I have read the post &lt;a href="https://doist.com/blog/get-everything-done-still-have-time-to-play/"&gt;Get Everything Done &amp;amp; Still Have Time to Play&lt;/a&gt; by Jackie Ashton recently and adopted some strategies into my process.&lt;/p&gt;
&lt;p&gt;Before I plan the tasks daily by allocating them into time slots in a calendar, it is tedious and time-wasting. It messes up my agenda, which is intended only to contain the events that I must do on time. And it makes me nervous and exhausted to follow a pre-defined schedule every day.&lt;/p&gt;
&lt;p&gt;My new process is much simpler. I review my life objectives first and group them into categories. Then I evaluate their importance in my current life stage and allocate my energy in percentage into them.&lt;/p&gt;
&lt;p&gt;Here is the list looks like now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maker 30%: Work as a programmer. I follow the suggestion from the lesson one &amp;ldquo;Switch Between Manager And Maker Modes&amp;rdquo; in the book &amp;ldquo;Leading Snowflakes.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Manager 5%: Work as a manager.&lt;/li&gt;
&lt;li&gt;Career 15%: The tasks that are not directly related to my work but help to improve myself on my career path.&lt;/li&gt;
&lt;li&gt;English 15%: I want to improve my English listening and speaking, so I commit enough time on it. However, it is a short term objective. Once I&amp;rsquo;m satisfied with my English level, I can move the time to other categories.&lt;/li&gt;
&lt;li&gt;Hobbits 10%: Piano, painting.&lt;/li&gt;
&lt;li&gt;Workout 10%: Running, swimming.&lt;/li&gt;
&lt;li&gt;Misc 5%: Such as reading novels.&lt;/li&gt;
&lt;li&gt;Family 10%: Quality time with family.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I create a punch card to plan timed bursts for each category. I use 50 minutes for tasks requiring long focus period, and 25 minutes for short focus period. The 50 minutes session counts for 10% of the energy, and 25 minutes accounts for 5% because I dedicate 10 hours per day to the planned tasks. And this is my punch card.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maker 50 / 50 / 50&lt;/li&gt;
&lt;li&gt;Career 50 / 25&lt;/li&gt;
&lt;li&gt;English 25 / 25 / 25&lt;/li&gt;
&lt;li&gt;Hobbits 25 / 25&lt;/li&gt;
&lt;li&gt;Workout 50&lt;/li&gt;
&lt;li&gt;Manager 25&lt;/li&gt;
&lt;li&gt;Misc 25&lt;/li&gt;
&lt;li&gt;Family 50&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have set up my OmniFocus projects hierarchy by categories. Every night, I go through each category to see what to do next and plan enough tasks for the next day according to the allocated energy.&lt;/p&gt;
&lt;p&gt;The next day, I choose a category and pick a task to work. If I have completed the timed burst, I checked it in the punch card, then move on to the next one.&lt;/p&gt;
&lt;p&gt;The new process has many pros:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is simple. Simplicity saves time and is efficient.&lt;/li&gt;
&lt;li&gt;It associates every task to one of my life objectives. It motivates me to complete the job if its objective is crucial for me. Otherwise, if I always feel reluctant to do a task, maybe I should cross on that objective from my life temporarily.&lt;/li&gt;
&lt;li&gt;I can dynamically adjust my day. If some routines occupy me in the most time of the day, I can use the remaining time to only the most critical categories.&lt;/li&gt;
&lt;li&gt;It reminds me of what is essential in my life. For example, I should spare enough time with my family and friends.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The cons are that I should take the punch card with me. But it is so simple that I can print a whole week into a single piece of A5 paper. As a bonus, I can review the card every week, record the completion status into a spreadsheet, and adjust my plan according to the statistics.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/life-management/">Life Management</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category></item><item><title>How to Mock Time in Rust Tests and Cargo Gotchas We Met</title><link>https://blog.iany.me/2019/03/how-to-mock-time-in-rust-tests-and-cargo-gotchas-we-met/</link><pubDate>Sun, 31 Mar 2019 06:22:22 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2019/03/how-to-mock-time-in-rust-tests-and-cargo-gotchas-we-met/</guid><description>&lt;p&gt;How to mock time in Rust tests and Cargo gotchas we met.
I&amp;rsquo;m working in a team developing a big Rust project recently. The project has some features depending on time. We, the developers, want to be able to mock the time in the test. In this post, I&amp;rsquo;ll talk about the problems we have met, mostly related to Cargo.&lt;/p&gt;
&lt;p&gt;I have created a GitHub repository &lt;a href="https://github.com/doitian/rust-mock-time-demo"&gt;doitian/rust-mock-time-demo&lt;/a&gt;, which contains all the following examples.&lt;/p&gt;
&lt;h2 id="the-first-attempt"&gt;The First Attempt&lt;/h2&gt;
&lt;p&gt;The requirement looks straightforward at first glance since Rust supports &lt;a href="https://doc.rust-lang.org/book/conditional-compilation.html"&gt;conditional compilation&lt;/a&gt; and cfg &lt;code&gt;test&lt;/code&gt; is only active in the test. We can implement a function telling us current time, and the whole program must fetch the current time from it. The function has two different versions, in the non-test code, it just returns the real current time. In the test, it is possible to mock the current time through a thread local variable.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;/// cfg-test/src/main.rs
use std::thread;
use std::time::{Duration, SystemTime, SystemTimeError};
#[cfg(not(test))]
pub fn now() -&amp;gt; SystemTime {
SystemTime::now()
}
#[cfg(test)]
pub mod mock_time {
use super::*;
use std::cell::RefCell;
thread_local! {
static MOCK_TIME: RefCell&amp;lt;Option&amp;lt;SystemTime&amp;gt;&amp;gt; = RefCell::new(None);
}
pub fn now() -&amp;gt; SystemTime {
MOCK_TIME.with(|cell| {
cell.borrow()
.as_ref()
.cloned()
.unwrap_or_else(SystemTime::now)
})
}
pub fn set_mock_time(time: SystemTime) {
MOCK_TIME.with(|cell| *cell.borrow_mut() = Some(time));
}
pub fn clear_mock_time() {
MOCK_TIME.with(|cell| *cell.borrow_mut() = None);
}
}
#[cfg(test)]
pub use mock_time::now;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;rsquo;s try it in both &lt;code&gt;cargo run&lt;/code&gt; and &lt;code&gt;cargo test&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cargo run -p cfg-test
cargo test -p cfg-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Work as expected, let&amp;rsquo;s call it a day.&lt;/p&gt;
&lt;h2 id="gochas-of-cfg-test"&gt;Gochas of cfg test&lt;/h2&gt;
&lt;p&gt;However, we immediately found the issue when we try to add the mockable &lt;code&gt;now&lt;/code&gt; into the project. The project is complex and organized into many crates, so we create a crate for this utility as well. However, the compiler complains that it cannot find the function &lt;code&gt;mock_time::set_mock_time&lt;/code&gt; when using the &lt;code&gt;cfg(test)&lt;/code&gt; only functions in the test in another crate. Since Cargo builds each file under tests directory in a standalone crate, see how it can reproduce the issue, see &lt;code&gt;cfg-test/tests/test_mock_time.rs&lt;/code&gt; reproduces this issue using the command below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RUSTFLAGS='--cfg cfg_test_crate_tests' cargo test -p cfg-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cause is that &lt;code&gt;cfg(test)&lt;/code&gt; does not pass though dependencies. The crate &lt;code&gt;test_mock_time&lt;/code&gt; in tests is built with &lt;code&gt;cfg(test)&lt;/code&gt;, but in its dependency, the crate &lt;code&gt;cfg-test&lt;/code&gt; , &lt;code&gt;cfg(test)&lt;/code&gt; is not set. In simple words, &lt;code&gt;cfg(test)&lt;/code&gt; is only set for the crate current in test.&lt;/p&gt;
&lt;h2 id="feature"&gt;Feature&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;Feature&amp;rdquo; is a well-known feature of Cargo, where a crate can customize how to build its dependencies. It is easy to switch to feature, just change &lt;code&gt;cfg(test)&lt;/code&gt; to &lt;code&gt;cfg(feature = &amp;quot;...&amp;quot;)&lt;/code&gt;, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;use std::time::SystemTime;
#[cfg(not(feature = &amp;quot;mock-time&amp;quot;))]
pub fn now() -&amp;gt; SystemTime {
SystemTime::now()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full example is in the &lt;code&gt;cfg-feature-lib&lt;/code&gt; directory in the repository.&lt;/p&gt;
&lt;p&gt;Since the feature does not turn on in test automatically, we have to remember to enable the feature when running test. What&amp;rsquo;s worse, the command line argument &lt;code&gt;--features&lt;/code&gt; does not pass to workspace members, it is only for the top project. Take the repository as an example:&lt;/p&gt;
&lt;p&gt;Running following command in the top directory does not enable feature &lt;code&gt;mock-time&lt;/code&gt; in &lt;code&gt;cfg-feature-test-manual&lt;/code&gt; crate.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cargo test --features mock-time -p cfg-feature-test-manual
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, it must be executed inside &lt;code&gt;cfg-feature-test-manual&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd cfg-feature-test-manual &amp;amp;&amp;amp; cargo test --features mock-time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After research, a trick comes out, which adds the dependency in both &lt;code&gt;dependencies&lt;/code&gt; and &lt;code&gt;dev-dependencies&lt;/code&gt;. The feature is only enabled in &lt;code&gt;dev-dependencies&lt;/code&gt;. See example &lt;code&gt;cfg-feature-test-auto/Cargo.toml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[dependencies]
cfg-feature-lib = { path = &amp;quot;../cfg-feature-lib&amp;quot; }
[dev-dependencies.cfg-feature-lib]
path = &amp;quot;../cfg-feature-lib&amp;quot;
features = [&amp;quot;mock-time&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We expect that &lt;code&gt;mock-time&lt;/code&gt; is automatically enabled in tests, but it turns out to be where weird things happen.&lt;/p&gt;
&lt;p&gt;In simple words:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the top workspace project has added the conditional dependencies in Cargo.toml, than the feature is always enabled, no matter in tests or final executables. See a demo in &lt;a href="https://github.com/quake/cargo-test/pull/1"&gt;this PR&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If the top workspace does not have such trick, then cargo behaves differently when whether &lt;code&gt;--all&lt;/code&gt; is specified or not. &lt;a href="https://github.com/quake/cargo-test/pull/2"&gt;Another PR&lt;/a&gt; demonstrates this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The feature based solution also has a inconvenient drawback. The feature can only be passed from a crate to its direct dependencies. To allow the time mock, a crate must be aware of whether its dependency depending on &lt;code&gt;time&lt;/code&gt; directly or indirectly.&lt;/p&gt;
&lt;p&gt;For example, if the crate &lt;code&gt;time&lt;/code&gt; has a feature &lt;code&gt;mock-time&lt;/code&gt;, and&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;package foo depends on bar, and bar depends on time.&lt;/li&gt;
&lt;li&gt;package root depends on foo, bar and time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then here is what root&amp;rsquo;s &lt;code&gt;Cargo.toml&lt;/code&gt; looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[dependencies]
foo = &amp;quot;0.1.0&amp;quot;
bar = &amp;quot;0.1.0&amp;quot;
[features]
mock-time = [&amp;quot;foo/mock-time&amp;quot;, &amp;quot;bar/mock-time&amp;quot;, &amp;quot;time/mock-time&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="final-solution"&gt;Final Solution&lt;/h1&gt;
&lt;p&gt;Because of the weird behavior of &lt;code&gt;--all&lt;/code&gt;, and how disturbing to set up the feature chain between dependencies, we decide to adopt &lt;code&gt;RUSTFLAGS&lt;/code&gt;. Indeed, I have used it once in an example above. &lt;code&gt;RUSTFLAGS&lt;/code&gt; is automatically enabled for all crates, no matter how deep the dependency is.&lt;/p&gt;
&lt;p&gt;However, there is another gotcha about &lt;code&gt;RUSTFLAGS&lt;/code&gt;. The doc test does not observe &lt;code&gt;RUSTFLAGS&lt;/code&gt;, it uses &lt;code&gt;RUSTDOCFLAGS&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We have built a crate &lt;a href="https://github.com/nervosnetwork/faketime"&gt;faketime&lt;/a&gt; from our experiences. Take a look if you are interested in mocking time in tests.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/cargo/">Cargo</category><category domain="https://blog.iany.me/tags/rust/">Rust</category></item><item><title>Rust Cell and RefCell</title><link>https://blog.iany.me/2019/02/rust-cell-and-refcell/</link><pubDate>Sun, 24 Feb 2019 07:35:47 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2019/02/rust-cell-and-refcell/</guid><description>&lt;p&gt;In Rust document, &lt;em&gt;Cell&lt;/em&gt; is “A mutable memory location”, and &lt;em&gt;RefCell&lt;/em&gt; is “A mutable memory location with dynamically checked borrow rules”.&lt;/p&gt;
&lt;p&gt;They both provide “interior mutability”, where you can modify the value stored in cell via immutable reference of the cell.&lt;/p&gt;
&lt;p&gt;They both have an API &lt;code&gt;get_mut&lt;/code&gt; to return a mutable reference to the underlying data. This method requires a mutable reference to the cell, which guarantees that the callee has exclusive ownership of the cell.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;pub fn get_mut(&amp;amp;mut self) -&amp;gt; &amp;amp;mut T
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The difference is how they implement interior mutability. &lt;em&gt;Cell&lt;/em&gt; copies or moves contained value, while &lt;em&gt;RefCell&lt;/em&gt; allows both mutable and immutable reference borrowing. I will try to explain the difference via their APIs in this article.&lt;/p&gt;
&lt;h2 id="cell"&gt;Cell&lt;/h2&gt;
&lt;p&gt;In the old version of Rust, &lt;em&gt;Cell&lt;/em&gt; requires the wrapped type to be &lt;em&gt;Copy&lt;/em&gt;. Many articles still contain such outdated and misleading information. Indeed &lt;em&gt;Cell&lt;/em&gt; has two different sets of APIs in a newer version.&lt;/p&gt;
&lt;p&gt;The first set is the Copy API. It requires a &lt;em&gt;Copy&lt;/em&gt; wrapper, and contains methods &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt;. The method &lt;code&gt;get&lt;/code&gt; returns a copy of the contained value, and &lt;code&gt;set&lt;/code&gt; stores a copy of the argument &lt;code&gt;val&lt;/code&gt; as the new value.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;// impl&amp;lt;T: Copy&amp;gt; Cell&amp;lt;T&amp;gt;
pub fn get(&amp;amp;self) -&amp;gt; T
// impl&amp;lt;T&amp;gt; Cell&amp;lt;T&amp;gt;
pub fn set(&amp;amp;self, val: T)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another set is the Move API. It has two methods &lt;code&gt;take&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt;. The method &lt;code&gt;take&lt;/code&gt; moves out the contained value, leaving &lt;code&gt;Default::default()&lt;/code&gt; in its place. The call &lt;code&gt;set&lt;/code&gt; also works for non-copyable type, where it moves the argument into the cell.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;// impl&amp;lt;T: Default&amp;gt; Cell&amp;lt;T&amp;gt;
pub fn take(&amp;amp;self) -&amp;gt; T
// impl&amp;lt;T&amp;gt; Cell&amp;lt;T&amp;gt;
pub fn set(&amp;amp;self, val: T)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The method &lt;code&gt;replace&lt;/code&gt; is an alternative of &lt;code&gt;take&lt;/code&gt;, when &lt;code&gt;T&lt;/code&gt; does not implement &lt;em&gt;Default&lt;/em&gt; or the new value is known in advance when taking the value.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-rust"&gt;// impl&amp;lt;T&amp;gt; Cell&amp;lt;T&amp;gt;
pub fn replace(&amp;amp;self, val: T) -&amp;gt; T
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An important property of &lt;em&gt;Cell&lt;/em&gt; is that the cell cannot tell you what&amp;rsquo;s contained in the cell via a reference. You either copy the contained value, or modify the cell and move out the value.&lt;/p&gt;
&lt;h2 id="refcell"&gt;RefCell&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;RefCell&lt;/em&gt; allows borrowing immutable or mutable reference to the contained value. It tracks the borrows at runtime, via &lt;code&gt;borrow&lt;/code&gt; and &lt;code&gt;borrow_mut&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pub fn borrow(&amp;amp;self) -&amp;gt; Ref&amp;lt;T&amp;gt;
pub fn borrow_mut(&amp;amp;self) -&amp;gt; RefMut&amp;lt;T&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The method &lt;code&gt;borrow&lt;/code&gt; grants temporary access to the contained value via immutable reference. Multiple immutable borrows can be taken out at the same time. It panics if the value is currently mutably borrowed.&lt;/p&gt;
&lt;p&gt;The API &lt;code&gt;borrow_mut&lt;/code&gt; mutably borrows the wrapped value. It panics if the value is currently borrowed, either mutably or immutably.&lt;/p&gt;
&lt;p&gt;The runtime tracking certainly has overheads, and &lt;code&gt;RefCell&lt;/code&gt; also can lead to runtime panics.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/memory-management/">Memory Management</category><category domain="https://blog.iany.me/tags/rust/">Rust</category></item><item><title>My Reading Workflow and IFTTT Applets</title><link>https://blog.iany.me/2019/01/my-reading-workflow-and-ifttt-applets/</link><pubDate>Sat, 12 Jan 2019 09:41:28 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2019/01/my-reading-workflow-and-ifttt-applets/</guid><description>&lt;p&gt;I categorize reading into 3 different categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Skim Reading: This includes the feeds I subscribed in Inoreader, &lt;a href="https://pinboard.in/u:iany/t:Updates/"&gt;websites&lt;/a&gt; I frequently visit and articles shared from my friends. I quickly skim them using fragmented time, usually less than half an hour in total per day, and save the articles which worth reading to Instapaper.&lt;/li&gt;
&lt;li&gt;Insensitive Reading: Instapaper sends me 20 unread articles to Kindle every morning. If I can spare at least 10 minutes, I will read them on the Kindle.&lt;/li&gt;
&lt;li&gt;Challenging Reading: Papers are definitely in this category. There are also articles hard to understand, I will print them into PDF. I schedule the time in advance, usually one hour or more, and read the PDF files on Sony Digital Paper.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll share the detailed workflow and the IFTTT applets used in the process.&lt;/p&gt;
&lt;p&gt;Sometimes, I also save some articles in Medium, so I have an applet to synchronize them to Instapaper.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Medium saved article → Instapaper&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I aggregate all the stuffs which I think worth to share into &lt;a href="https://buffer.com/"&gt;Buffer&lt;/a&gt;. There is a large bundle of IFTTT applets for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My new blog post → Buffer&lt;/li&gt;
&lt;li&gt;Instapaper liked → Buffer&lt;/li&gt;
&lt;li&gt;Inoreader starred → Buffer&lt;/li&gt;
&lt;li&gt;Medium Applause → Buffer&lt;/li&gt;
&lt;li&gt;Reddit → Buffer&lt;/li&gt;
&lt;li&gt;Youtube liked → Buffer&lt;/li&gt;
&lt;li&gt;Pinboard tagged &lt;em&gt;tt&lt;/em&gt; → Buffer&lt;/li&gt;
&lt;li&gt;500px → Buffer&lt;/li&gt;
&lt;li&gt;Lofter → Buffer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Buffer schedules the time and will finally publish to Twitter.&lt;/p&gt;
&lt;p&gt;Buffer also acts as my sharing hub. I forward the shared messages to two other places, Telegram channel and Inoreader saved web pages.&lt;/p&gt;
&lt;p&gt;Following applets publish to my &lt;a href="https://t.me/ianshare"&gt;Telegram channel&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Buffer → Telegram &lt;em&gt;ianshare&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Liked Tweets → Telegram &lt;em&gt;ianshare&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;New journal post → Telegram &lt;em&gt;ianshare&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The similar group of applets save them into Inoreader.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Buffer → Inoreader tagged &lt;em&gt;ianFeed&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Liked tweets → Inoreader tagged &lt;em&gt;ianFeed&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;New journal post → Inoreader tagged &lt;em&gt;ianFeed&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I tagged the saved articles, so Inoreader allows me to share it as an &lt;a href="https://www.inoreader.com/stream/user/1005740962/tag/ianFeed"&gt;RSS feed&lt;/a&gt; or an &lt;a href="https://www.inoreader.com/stream/user/1005740962/tag/ianFeed/view/html?cs=m"&gt;HTML page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I review these shared articles weekly, and include them in my the &lt;a href="https://u.iany.me/journal"&gt;journal posts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The last, I backup the data into Google Drive spreadsheets via these applets.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My tweets → Google Drive&lt;/li&gt;
&lt;li&gt;Liked tweets → Google Drive&lt;/li&gt;
&lt;li&gt;Pinboard → Google Drive&lt;/li&gt;
&lt;li&gt;Buffer → Google Drive&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/knowledge-management/">Knowledge Management</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category></item><item><title>Weekly Paper: Liberal Radicalism</title><link>https://blog.iany.me/2018/12/weekly-paper-liberal-radicalism/</link><pubDate>Sat, 01 Dec 2018 12:06:51 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2018/12/weekly-paper-liberal-radicalism/</guid><description>&lt;p&gt;&lt;a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3243656"&gt;paper source&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(LR is) as flexible and responsive as the market, but avoids free-rider problems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Flexible and responsive: any one can propose a new public good project, and the project can get enough fundings even when only a small community funds it.&lt;/li&gt;
&lt;li&gt;Subsidies create incentives for citizens to fund projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;(Free-rider problem is) due to the expense or inefficiency involved in excluding individuals from access.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="cons-of-existing-solutions"&gt;Cons of Existing Solutions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1p1v&lt;/strong&gt;: oppress minorities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capitalism&lt;/strong&gt;: inefficiently exclude potential users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Charitable organization&lt;/strong&gt;: difficult to closely align reliably with the common good.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QV&lt;/strong&gt;: it doesn’t solve the problem of flexibility, a.k.a., it requires a curated projects list.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="model"&gt;Model&lt;/h2&gt;
&lt;h3 id="assumptions"&gt;Assumptions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;We can verifiably distinguish among and identify these citizens.&lt;/li&gt;
&lt;li&gt;Any citizen may at any time propose a new public good.&lt;/li&gt;
&lt;li&gt;Our interest here is in maximization of dollar-equivalent value rather than achieving an equitable distribution of value.&lt;/li&gt;
&lt;li&gt;Utility function V is concave (国内的叫法一般是反的，即我们平常说的凸函数), smooth, increasing.&lt;/li&gt;
&lt;li&gt;The deficit is not bounded, a.k.a, the funding solution can collect unbounded taxes.&lt;/li&gt;
&lt;/ul&gt;
&lt;!--more--&gt;
&lt;h3 id="optimization-target-function"&gt;Optimization Target Function&lt;/h3&gt;
&lt;p&gt;Given:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let &lt;code&gt;$V_i^p(F^p)$&lt;/code&gt; be the currency-equivalent utility function. It is the benefit that the citizen &lt;code&gt;$i$&lt;/code&gt; can get when the project &lt;code&gt;$p$&lt;/code&gt; is funded with &lt;code&gt;$F^p$&lt;/code&gt; units of currency.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;$i \times p$&lt;/code&gt; contributions matrix &lt;code&gt;$\left\{c_i^p\right\}_i^p$&lt;/code&gt;, where &lt;code&gt;$c_i^p$&lt;/code&gt; is the contribution citizen &lt;code&gt;$i$&lt;/code&gt; makes to project &lt;code&gt;$p$&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need to find a funding distribution solution &lt;code&gt;$\left\{F^p\right\}^p$&lt;/code&gt; which is a p-dimention vector. &lt;code&gt;$F^p$&lt;/code&gt; is the funds allocated to project &lt;code&gt;$p$&lt;/code&gt;. The solution maximize:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\sum _ { p } \left(V _ { i } ^ { p } \left( F ^ { p } \right) - c _ { i } ^ { p }\right) - t _ { i }
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\sum _ { i } t _ { i } = \sum _ { p } \left( F ^ { p } - \sum _ { i } c _ { i } ^ { p } \right)
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since V is concave, smooth and increasing, it is easy to find the maximum using the first order derivative, which gives&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
V ^ { p ^ { \prime } } = 1
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="capitalism"&gt;Capitalism&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
F ^ { p } = \sum _ { i } c _ { i } ^ { p }
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
V ^ { p ^ { \prime } } = N
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="1p1v"&gt;1p1v&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
N \cdot \operatorname{Median}_{i} V_{i} ^ { p ^ { \prime } } \left( F ^ { P } \right) = 1
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The optimal solution requires mean, where median is absolutely different with mean.&lt;/p&gt;
&lt;h3 id="lr"&gt;LR&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
F ^ { p } = \left( \sum _ { i } \sqrt { c _ { i } ^ { p } } \right) ^ { 2 }
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;(We) assume that citizens ignore their impact on the budget and costs imposed by it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After incorporating the deficit:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ V ^ { p ^ { \prime } } \approx 1 + \Lambda \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is assumed that &lt;code&gt;$\Lambda$&lt;/code&gt; is on the order of &lt;code&gt;$1/N$&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="extensions"&gt;Extensions&lt;/h2&gt;
&lt;h3 id="budgeted-matching-funds"&gt;Budgeted matching funds&lt;/h3&gt;
&lt;p&gt;CLR: linear combine LR and Capitalism until the deficit is under the budget.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ F ^ { p } = \alpha \left( \sum _ { i } \sqrt { c _ { i } ^ { p } } \right) ^ { 2 } + ( 1 - \alpha ) \sum _ { i } c _ { i } ^ { p } \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LR allows every project gets the optimal funding by incentivize citizens via the deficit. CLR is more practically, because in real world, there is always a budget.&lt;/p&gt;
&lt;h3 id="collusion"&gt;Collusion&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Coercion Resistance&lt;/strong&gt;: A voter cannot prove to anyone else who they voted for (or even, ideally, whether or not they voted) even if they wanted to.&lt;/p&gt;
&lt;h3 id="negative-contributions"&gt;Negative contributions&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;More broadly, negative contributions may be a quite powerful way to deter collusive schemes as they offer a way for any citizen to be a “vigilante enforcer” against fraud and abuse.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On the other-side, it also can be used to attack and threaten other communities.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/series/weekly-paper/">Weekly Paper</category><category domain="https://blog.iany.me/tags/economics/">Economics</category><category domain="https://blog.iany.me/tags/governance/">Governance</category></item><item><title>Weekly Paper: 1945 - Hayek - The Use of Knowledge in Society</title><link>https://blog.iany.me/2018/11/weekly-paper-hayek-the-use-of-knowledge-in-society/</link><pubDate>Sun, 25 Nov 2018 06:15:15 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2018/11/weekly-paper-hayek-the-use-of-knowledge-in-society/</guid><description>&lt;blockquote&gt;
&lt;p&gt;Economic problem of society is mainly one of rapid &lt;strong&gt;adaptation to changes&lt;/strong&gt; in &lt;strong&gt;the particular circumstances of time and place&lt;/strong&gt;, it would seem to follow that the ultimate decisions must be left to the people who are familiar with these circumstances, who know directly of the relevant changes and of the resources immediately available to meet them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The knowledge is dispersed and rapid changes. To take advantage of this knowledge, people must make decision distributively. People require only relevant information to make better decisions. This communicating mechanism should be&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;communicating to him such further information as he needs to fit his decisions into the whole pattern of changes of the larger economic system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;It is always a question of the relative importance of the particular things with which he is concerned, and the causes which alter their relative importance are of no interest.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The price system solved it, and&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We must look at the price system as such a mechanism for communicating information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is an effective communicating system because:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Only the most essential information is passed on and passed on only to those concerned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It extends the span of our utilization of resources without the control of any one mind. Also, it will make the individuals do the desirable things without anyone having to tell them what to do.&lt;/p&gt;
&lt;h2 id="thoughts"&gt;Thoughts&lt;/h2&gt;
&lt;p&gt;It opened my mind to look at the price system differently. It is an effective way to watch the only the necessary and relevant information, and the messages are propagated rapidly in the system. It makes distributed decisions possible and outperforms central decision authorities in most cases.&lt;/p&gt;
&lt;p&gt;Although distributed decisions can achieve local optimum, which may not be the global optimum. We human may also need some centralization to break the equilibrium by a trial and error like method.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.jstor.org/stable/1809376?seq=1#page_scan_tab_contents"&gt;Paper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bootsoon.github.io/economics/2017/04/25/038.html"&gt;第038讲丨知识在社会中的运用&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/series/weekly-paper/">Weekly Paper</category><category domain="https://blog.iany.me/tags/economics/">Economics</category></item><item><title>Weekly Paper: Helix</title><link>https://blog.iany.me/2018/11/weekly-paper-helix/</link><pubDate>Sat, 10 Nov 2018 03:49:28 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2018/11/weekly-paper-helix/</guid><description>&lt;p&gt;&lt;a href="https://www.orbs.com/introducing-helix-the-orbs-consensus-algorithm/"&gt;Orbs - Read the Helix Consensus Algorithm White Paper&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hazel is a &lt;strong&gt;Byzantine fault-tolerant&lt;/strong&gt; and &lt;strong&gt;scalable&lt;/strong&gt; consensus algorithm for the fair ordering of transactions among nodes in a distributed network.&lt;/p&gt;
&lt;p&gt;It assumed that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node-node connections are assumed to be strongly synchronous.&lt;/li&gt;
&lt;li&gt;There is a known bound for the faulty nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is scalable because the PBFT committee size is bounded.&lt;/p&gt;
&lt;p&gt;Helix archives fairness in these aspects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Committee selection relies on reputation and a random seed.&lt;/li&gt;
&lt;li&gt;Transactions are randomly selected when they are encrypted.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both committee election and transactions sampling utilize a random seed derived from the previous decrypted block.&lt;/p&gt;
&lt;p&gt;Helix nodes validate block transaction by checking the distribution overlap with local transaction pool.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/series/weekly-paper/">Weekly Paper</category><category domain="https://blog.iany.me/tags/consensus/">Consensus</category><category domain="https://blog.iany.me/tags/distributed-system/">Distributed System</category></item><item><title>Flatbuffers Compatible Table</title><link>https://blog.iany.me/2018/11/flatbuffers-compatible-table/</link><pubDate>Fri, 02 Nov 2018 02:00:36 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2018/11/flatbuffers-compatible-table/</guid><description>&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;parent&lt;/th&gt;
&lt;th&gt;table&lt;/th&gt;
&lt;th&gt;struct&lt;/th&gt;
&lt;th&gt;union&lt;/th&gt;
&lt;th&gt;vector&lt;/th&gt;
&lt;th&gt;string&lt;/th&gt;
&lt;th&gt;enum&lt;/th&gt;
&lt;th&gt;scalar&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;root&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;table&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;struct&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;union&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vector&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;td&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If there is a &lt;code&gt;Y&lt;/code&gt; in row A column B, B can be used as a child of A. If it is
blank, B is not allowed as a child of A.&lt;/p&gt;
&lt;p&gt;Parent &lt;code&gt;root&lt;/code&gt; means which type can be used as a root object.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;• &lt;a href="https://github.com/doitian/flatbuffers_compatible_table/blob/master/compatible_test.fbs"&gt;the test
schema&lt;/a&gt;&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/flatbuffers/">Flatbuffers</category><category domain="https://blog.iany.me/tags/serialization/">Serialization</category></item><item><title>Decred Review</title><link>https://blog.iany.me/2018/07/decred-review/</link><pubDate>Thu, 19 Jul 2018 01:24:39 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2018/07/decred-review/</guid><description>&lt;p&gt;&lt;a href="https://docs.decred.org/research/overview/"&gt;Official References&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Based on Bitcoin&lt;/li&gt;
&lt;li&gt;PoS overlays upon PoW&lt;/li&gt;
&lt;li&gt;Add signature algorithms Ed25519 and Schnorr&lt;/li&gt;
&lt;li&gt;Use BLAKE-256 as hash algorithm&lt;/li&gt;
&lt;li&gt;Enable segwit to solve transaction malleability&lt;/li&gt;
&lt;li&gt;Transaction expiration mechanism&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hybrid-consensus"&gt;Hybrid Consensus&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://decredible.com/mining/hybrid-consensus/"&gt;Decredible | Decred hybrid consensus explained&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PoW miners propose new block.&lt;/li&gt;
&lt;li&gt;PoS miners vote whether accepting the block. The ticket should be purchased first. 5 tickets are selected randomly using the new PoW block header. Block is accepted if 3 tickets agree.&lt;/li&gt;
&lt;li&gt;Block reward is split between PoW and PoS miners.&lt;/li&gt;
&lt;li&gt;PoS tickets are selected using PoW block header. It guaranteed that is has a very high cost to manipulate picking tickets.&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/series/blockchain-projects-review/">Blockchain Projects Review</category><category domain="https://blog.iany.me/tags/blockchain/">Blockchain</category><category domain="https://blog.iany.me/tags/consensus/">Consensus</category><category domain="https://blog.iany.me/tags/crypto-currency/">Crypto Currency</category></item><item><title>Vim Marks Stack</title><link>https://blog.iany.me/2018/05/vim-marks-stack/</link><pubDate>Thu, 31 May 2018 03:30:42 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2018/05/vim-marks-stack/</guid><description>&lt;p&gt;I rarely used more than two marks in Vim, so why not use them as a stack and keep latest history?&lt;/p&gt;
&lt;p&gt;The idea is that&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First shift existing marks in &lt;code&gt;a-y&lt;/code&gt; to &lt;code&gt;b-z&lt;/code&gt;, thus original mark &lt;code&gt;z&lt;/code&gt; is dropped.&lt;/li&gt;
&lt;li&gt;Then save current position into mark &lt;code&gt;a&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;function! PushMark(is_global)
if a:is_global
let l:curr = char2nr('Z')
else
let l:curr = char2nr('z')
endif
let l:until = l:curr - 25
while l:curr &amp;gt; l:until
call setpos(&amp;quot;'&amp;quot; . nr2char(l:curr), getpos(&amp;quot;'&amp;quot; . nr2char(l:curr - 1)))
let l:curr -= 1
endwhile
call setpos(&amp;quot;'&amp;quot; . nr2char(l:curr), getpos(&amp;quot;.&amp;quot;))
endfunction
&amp;quot; Push to marks a-z
nnoremap &amp;lt;silent&amp;gt; m, :call PushMark(0)&amp;lt;CR&amp;gt;
&amp;quot; Push to marks A-Z
nnoremap &amp;lt;silent&amp;gt; m. :call PushMark(1)&amp;lt;CR&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category><category domain="https://blog.iany.me/tags/vim/">Vim</category></item><item><title>Read Various RSA Keys in Ruby</title><link>https://blog.iany.me/2017/11/read-various-rsa-keys-in-ruby/</link><pubDate>Sat, 18 Nov 2017 10:46:07 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2017/11/read-various-rsa-keys-in-ruby/</guid><description>&lt;p&gt;I recently worked in a Ruby on Rails project which should integrate with many different payment systems. There system mostly use RSA in encryption and signature. However they provide the RSA keys in different formats, it is a challenge to choose a right way to read the keys in Ruby.&lt;/p&gt;
&lt;p&gt;RSA is an asymmetric cryptographic algorithm, thus it requires two keys, private key and public key. The key itself is just binary, but it can be encoded in different format.&lt;/p&gt;
&lt;h1 id="derpem"&gt;DER/PEM&lt;/h1&gt;
&lt;p&gt;DER is a binary encoding method. It is consisted of unreadable characters. The extension is usually &lt;code&gt;.cer&lt;/code&gt;, &lt;code&gt;.crt&lt;/code&gt;, or &lt;code&gt;.der&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;PEM encode key using base64, then add PEM header and footer around key. For example&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx4c0F2TEoSe7wMBnn4WA
CSWQZL82eJJG3g128dE1BAMPAcSx0yaLWAEZJ0iZh9q14YND4kh7Or1hkV+beJfo
1c7DjO+VA31l9Nzdps/jGzkxa926VlFoXlLHngBn+zglfmXrhlVNVle/6asDrW49
p8LlLBZHqi/P72f9WUlAa7q45XXY48OqgsJ4ok2Xo0ZbLL9EHAu1GyfGFkBOsnOz
pqJe/aE0gltk+O7dKlVS1bGwm9cwx2eo+mEbH7NgbUm/by/OJKFx1SDbMIJRFcUj
WlosuKfzzkafH4i+z7n07s+StQ6kW5TcZbVVeQPjyzPdWeKvTRZMIvHV3ANVll+i
YQIDAQAB
-----END PUBLIC KEY-----
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the most frequently used format, since &lt;code&gt;ssh-keygen&lt;/code&gt; generate private key in PEM format. The PEM key file extension is usually &lt;code&gt;.pem&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Both DER and PEM format can be read in Ruby using following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;k = OpenSSL::PKey::RSA.new(File.read(&amp;quot;/path/to/keyfile&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the key file has a passphrase, it can be specified as the second argument.&lt;/p&gt;
&lt;p&gt;The method &lt;code&gt;k.private?&lt;/code&gt; can be used to check whether the key is a private key. Since public key can be calculated from private key, public key is always available.&lt;/p&gt;
&lt;p&gt;Sometimes, the key is provided just in base64 format without PEM header. There are two solutions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Base64 decode the string and pass it as key content&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;k = OpenSSL::PKey::RSA.new(Base64.decode64(key_content))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Format it using PEM and then read the key&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;pem = key_content.gsub(&amp;quot;\r\n&amp;quot;, &amp;quot;&amp;quot;).scan(/.{1,64}/).join(&amp;quot;\n&amp;quot;)
# or use BEGIN PRIVATE KEY
pem = &amp;quot;-----BEGIN PUBLIC KEY-----\n#{pem}\n-----END PUBLIC KEY-----\n&amp;quot;
k = OpenSSL::PKey::RSA.new(pem)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="pkcs12"&gt;PKCS12&lt;/h1&gt;
&lt;p&gt;PKCS12 key file extension is usually &lt;code&gt;.p12&lt;/code&gt; or &lt;code&gt;.pfx&lt;/code&gt;. It is a frequently used format in browsers to export certificates. Most PKCS12 files are protected using password, which is the optional second argument in the constructor.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;pkcs12 = OpenSSL::PKCS12.new(key_content, certificate_password)
k = pkcs12.key
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="x509-certificate"&gt;X509 Certificate&lt;/h1&gt;
&lt;p&gt;X509 Certificate adds some meta information to key, such as issuer, expiration date. X509 can be encoded using DER or PEM, the key file extension is usually &lt;code&gt;.cer&lt;/code&gt;. In PEM format, the header is &lt;code&gt;BEGIN CERTIFICATE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;X509 Certificate can be read as following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;cert = OpenSSL::X509::Certificate.new(File.read(&amp;quot;/path/to/certfile&amp;quot;))
k = cert.public_key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When asn1 error is thrown, try switch between &lt;code&gt;OpenSSL::X509::Certificate&lt;/code&gt; and &lt;code&gt;OpenSSL::PKey::RSA&lt;/code&gt;.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/cryptography/">Cryptography</category><category domain="https://blog.iany.me/tags/openssl/">OpenSSL</category><category domain="https://blog.iany.me/tags/rsa/">RSA</category><category domain="https://blog.iany.me/tags/ruby/">Ruby</category></item><item><title>Nginx as UDP Load Balance</title><link>https://blog.iany.me/2017/08/nginx-udp-load-balance/</link><pubDate>Wed, 23 Aug 2017 23:11:35 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2017/08/nginx-udp-load-balance/</guid><description>&lt;p&gt;Just upgraded Graylog to cluster in a project. Because syslog UDP is used as input, a UDP load balance is required to distribute logs to servers in the cluster. Since the servers are hosted in Aliyun, I tried Aliyun UDP Load Balance first. But it does not forward requests evenly, and health detection diagram cannot be disabled. The popular HTTP load balance tool HAProxy does not support UDP. Fortunately, Nginx can be used as a UDP load balance.&lt;/p&gt;
&lt;p&gt;First, the latest version of Nginx is required to support &lt;code&gt;stream&lt;/code&gt; directive. For example, the default package in Ubuntu 14.04 does not support it, thus use PPA or compile from source.&lt;/p&gt;
&lt;p&gt;The PPA version requires loading the feature from dynamic library. Add following line in the beginning of &lt;code&gt;nginx.conf&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;load_module /usr/lib/nginx/modules/ngx_stream_module.so;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following snippet configures Nginx listens on UDP 1515, and forward requests evenly to the 1514 UDP port in three different servers. The snippet must be at the top level of &lt;code&gt;nginx.conf&lt;/code&gt;. Files in &lt;code&gt;sites-enabled&lt;/code&gt; usually are embedded in &lt;code&gt;http&lt;/code&gt; directive, do not put stream config there.&lt;/p&gt;
&lt;p&gt;The option &lt;a href="http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_responses"&gt;proxy_responses&lt;/a&gt; is the number of packets expected from backend servers in responded to client. It should be configured according to scenario. In my case, I use it to accept logs forwarded from &lt;code&gt;rsyslog&lt;/code&gt;. The client does not expect response, and the server does not respond at all. If the option is not set to 0, Nginx will refuse new connection after used up all of them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stream {
upstream syslog_udp {
server logs1-xy.example.com:1514;
server logs2-xy.example.com:1514;
server logs3-xy.example.com:1514;
}
server {
listen 1515 udp;
proxy_pass syslog_udp;
proxy_responses 0;
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because there are so many logs requests, the open file limit and worker connections must be set to a large value.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;worker_rlimit_nofile 1000000;
events {
worker_connections 20000;
}
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/nginx/">Nginx</category></item><item><title>Redis as Write Buffer</title><link>https://blog.iany.me/2017/08/redis-write-buffer/</link><pubDate>Sat, 19 Aug 2017 23:41:07 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2017/08/redis-write-buffer/</guid><description>&lt;p&gt;It is common to use Redis as read buffer。To read data, first check whether it exists in Redis. If so, use the cached data, otherwise read from the backend storage and save a copy into Redis. To write data, first save into backend storage, then clear or update Reids cache.&lt;/p&gt;
&lt;p&gt;But if the system bottleneck is in writing, the solution above does not work. But it is easy to modify it into a write buffer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read: Check whether the data exists in Redis. Read from backend storage if not.&lt;/li&gt;
&lt;li&gt;Write: Just write into Redis. Notify background worker via message queue to flush the cache into backend storage.&lt;/li&gt;
&lt;li&gt;The background worker watches message queue, save data and delete from Redis.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The message queue can be implemented using Redis LIST. Official &lt;a href="https://redis.io/commands/rpoplpush/"&gt;RPOPLPUSH – Redis&lt;/a&gt; command document already described how to implement a reliable queue. The remaining issue is how to safely delete saved data from Redis.&lt;/p&gt;
&lt;p&gt;The save step can be split into:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read from Redis.&lt;/li&gt;
&lt;li&gt;Save into backend storage.&lt;/li&gt;
&lt;li&gt;Delete from Redis.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If new change comes between 1 and 3, the change is discarded in step 3. It must be resolved using lock, or transaction.&lt;/p&gt;
&lt;p&gt;After research, it is a bit complex to &lt;a href="https://redis.io/docs/reference/patterns/distributed-locks/"&gt;implement a lock&lt;/a&gt;. Fortunately, it is easy to use transaction.&lt;/p&gt;
&lt;p&gt;Redis provides &lt;code&gt;MULTI&lt;/code&gt; and &lt;code&gt;EXEC&lt;/code&gt; to wrap several commands into a transaction. Although it does not support rollback on error, it guarantees that either none of the commands are executed, or all of them have been executed. Command &lt;code&gt;WATCH&lt;/code&gt; can monitor the changes on a key. If the key changes after &lt;code&gt;WATCH&lt;/code&gt; and before executing the transaction, the transaction is cancelled. See details in official document &lt;a href="https://redis.io/docs/manual/transactions/"&gt;here&lt;/a&gt;. So the save step can be implemented in following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;WATCH&lt;/code&gt; the key to flush&lt;/li&gt;
&lt;li&gt;Read from Redis.&lt;/li&gt;
&lt;li&gt;Save into backend storage.&lt;/li&gt;
&lt;li&gt;Wrap command in &lt;code&gt;MULTI&lt;/code&gt; and &lt;code&gt;EXEC&lt;/code&gt; to delete the key from Redis&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pseudocode：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;redis.send('WATCH', key)
data = redis.get(key)
database.save(key, data)
redis.send('MULTI')
redis.send('DEL', key)
redis.send('EXEC')
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/redis/">Redis</category></item><item><title>Resource Bundle in IntelliJ TornadoFX Gradle Project</title><link>https://blog.iany.me/2017/06/resource-bundle-in-intellij-tornadofx-gradle-project/</link><pubDate>Sat, 03 Jun 2017 16:09:41 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2017/06/resource-bundle-in-intellij-tornadofx-gradle-project/</guid><description>&lt;ul&gt;
&lt;li&gt;Add properties files into &lt;code&gt;src/main/resources&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Global messages location:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/main/resources/Messages.properties&lt;/code&gt; is for default locale&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/main/resources/Messages_en_US.properties&lt;/code&gt; is for locale &lt;code&gt;en_US&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resource Bundle for component: Same directory structure to the class path. For example the resource bundle file for &lt;code&gt;views.Main&lt;/code&gt; can be found in &lt;code&gt;src/main/resources/views/Main.properties&lt;/code&gt;, or file name &lt;code&gt;Main_en_US.properties&lt;/code&gt; for specific locale.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.jetbrains.com/help/idea/configuring-encoding-for-properties-files.html"&gt;Enable native-to-ascii conversion&lt;/a&gt; in IntelliJ to ease editing UTF-8 values.&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/java/">Java</category></item><item><title>Capture Anything using macOS Javascript automation</title><link>https://blog.iany.me/2017/03/capture-anything-using-macos-javascript-automation/</link><pubDate>Sun, 19 Mar 2017 17:54:08 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2017/03/capture-anything-using-macos-javascript-automation/</guid><description>&lt;p&gt;I have written many scripts to automate the work in macOS. This one is the most frequently used one. The script can capture the current selection in frontend most app in OmniFocus, and I can jump back to the app using URL.&lt;/p&gt;
&lt;p&gt;It only supports apps I frequently use. New app support can be added in &lt;code&gt;capturers&lt;/code&gt; map.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;var tasks = [];
var app = null;
// Uncomment following line to debug a specific app.
// var app = Application(&amp;quot;Evernote&amp;quot;);
(function(app, tasks) {
var bundleIdentifier;
if (app === null || app === undefined) {
var systemEvents = Application(&amp;quot;System Events&amp;quot;);
var bundleIdentifier = systemEvents.processes.whose({ frontmost: true })[0].bundleIdentifier();
app = Application(bundleIdentifier);
} else {
bundleIdentifier = app.id();
}
if (tasks === null || tasks === undefined) {
tasks = [];
}
var capturers = {
Safari: function(app, tasks) {
var tab = app.windows[0].currentTab();
tasks.push({ name: tab.name(), note: tab.url() });
},
Chromium: function(app, tasks) {
var tab = app.windows[0].activeTab();
tasks.push({ name: tab.title(), note: tab.url() });
},
Finder: function(app, tasks) {
app.selection().forEach(function(file) {
var name = file.name();
var url = file.url();
var directory = file.container().url();
tasks.push({ name: &amp;quot;[File] &amp;quot; + name, note: url + &amp;quot; in directory &amp;quot; + directory });
});
},
Contacts: function(app, tasks) {
app.selection().forEach(function(contact) {
tasks.push({ name: &amp;quot;Contact &amp;quot; + contact.name(), note: &amp;quot;addressbook://&amp;quot; + contact.id() });
});
},
Evernote: function(app, tasks) {
app.selection().forEach(function(note) {
var url = note.noteLink();
var title = note.title();
tasks.push({ name: &amp;quot;[Evernote] &amp;quot; + title, note: url });
});
},
Mail: function(app, tasks) {
app.selection().forEach(function(mail) {
var url = encodeURI(&amp;quot;message://&amp;lt;&amp;quot; + mail.messageId() + &amp;quot;&amp;gt;&amp;quot;);
var title = mail.sender() + &amp;quot;: &amp;quot; + mail.subject();
tasks.push({ name: title, note: url });
});
}
};
capturers[&amp;quot;Google Chrome&amp;quot;] = capturers.Chromium;
var fluidCapturer = function(app, tasks) {
var tab = app.browserWindows[0].selectedTab();
var name = &amp;quot;[&amp;quot; + app.name() + &amp;quot;] &amp;quot; + tab.title();
tasks.push({ name: name, note: tab.url() });
};
var defaultCapturer = function(app, tasks) {
app.includeStandardAdditions = true;
var url = encodeURI(&amp;quot;file://&amp;quot; + app.pathTo().toString());
tasks.push({ name: app.name(), note: url })
};
var capture = capturers[app.name()];
if (capture === undefined) {
if (bundleIdentifier.startsWith(&amp;quot;com.fluidapp.FluidApp.&amp;quot;)) {
capture = fluidCapturer;
} else {
capture = defaultCapturer;
}
}
capture(app, tasks);
return tasks;
})(app, tasks);
// Then send tasks to OmniFocus or other app which supports Applescript automation.
var of = Application(&amp;quot;OmniFocus&amp;quot;);
var inbox = of.quickEntry().inboxTasks;
tasks.forEach(function(task) {
inbox.push(of.Task(task));
});
of.quickEntry().open();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use &lt;a href="https://www.keyboardmaestro.com/main/"&gt;Keyboard Maestro&lt;/a&gt; to invoke the script using keyboard shortcut.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/javascript/">JavaScript</category><category domain="https://blog.iany.me/tags/macos/">macOS</category></item><item><title>Vcpkg static linking</title><link>https://blog.iany.me/2017/03/vcpkg-static-linking/</link><pubDate>Sun, 05 Mar 2017 12:13:07 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2017/03/vcpkg-static-linking/</guid><description>&lt;p&gt;&lt;a href="https://github.com/Microsoft/vcpkg"&gt;Vcpkg&lt;/a&gt; is a tool published by Microsoft, which is used to manage C/C++ libraries in Windows. It makes libraries installation easier, and it works well with CMake.&lt;/p&gt;
&lt;p&gt;But it is not straitforward to staticly link the depdendent libraries using vcpkg.&lt;/p&gt;
&lt;p&gt;Under Linux，static linking just need to use &lt;code&gt;.a&lt;/code&gt; files instead of &lt;code&gt;.so&lt;/code&gt; files (similar in macOS). Use &lt;code&gt;boost&lt;/code&gt; as an example, the bundled &lt;code&gt;FindBoost&lt;/code&gt; returns found static library files in &lt;code&gt;Boost_LIBRARIES&lt;/code&gt; when setting following flags.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmake"&gt;set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then just use &lt;code&gt;Boost_LIBRARIES&lt;/code&gt; in &lt;code&gt;link_libraries&lt;/code&gt; or &lt;code&gt;target_link_libraries&lt;/code&gt; to statically link boost.&lt;/p&gt;
&lt;p&gt;But when these two flags are set in Windows using vcpkg, CMake will complain that it cannot find static libraries of boost. It seems a constraint of VC compiler, so &lt;a href="https://blogs.msdn.microsoft.com/vcblog/2016/11/01/vcpkg-updates-static-linking-is-now-available/"&gt;vcpkg requires install the static version of libraries explicitly&lt;/a&gt;. For example, to install boost static 64bit libraries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vcpkg install zlib:x64-windows-static
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;VCPKG_TARGET_TRIPLET&lt;/code&gt; is required to generate VC project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake .. \
-DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg.cmake \
-DVCPKG_TARGET_TRIPLET=x64-windows-static \
-G &amp;quot;Visual Studio 14 2015 Win64&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the project generated throws error in linking. It can be fixed referring this Stack Overflow thread: &lt;a href="http://stackoverflow.com/questions/14172856/cmake-compile-with-mt-instead-of-md"&gt;CMake compile with /MT instead of /MD&lt;/a&gt;. Here is the final CMake sample:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmake"&gt;cmake_minimum_required(VERSION 3.0)
project(static_link_test CXX)
set(CMAKE_CXX_STANDARD 11)
if(MSVC)
string(REPLACE &amp;quot;/MD&amp;quot; &amp;quot;/MT&amp;quot; CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
string(REPLACE &amp;quot;/MD&amp;quot; &amp;quot;/MT&amp;quot; CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
string(REPLACE &amp;quot;/MD&amp;quot; &amp;quot;/MT&amp;quot; CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
else(MSVC)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON)
endif(MSVC)
find_package(Boost 1.36.0 REQUIRED COMPONENTS filesystem program_options)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
${Boost_INCLUDE_DIRS}
)
link_libraries(
${Boost_LIBRARIES}
)
add_executable(static_link_test
src/main.cpp
)
install(TARGETS static_link_test RUNTIME DESTINATION bin)
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/cpp/">C++</category><category domain="https://blog.iany.me/tags/windows/">Windows</category></item><item><title>Deploy Concourse CI using Docker</title><link>https://blog.iany.me/2017/02/concourse-ci-docker/</link><pubDate>Sat, 25 Feb 2017 18:46:03 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2017/02/concourse-ci-docker/</guid><description>&lt;p&gt;&lt;a href="https://concourse-ci.org/"&gt;Concourse CI&lt;/a&gt; is an awesome open source continuous integration tool. If you are not using Gitlab, and want to setup a CI server, it is a good choice.&lt;/p&gt;
&lt;p&gt;Concourse CI provides Docker image and docker compose sample config. But when I run the hello world example, I have met several problems.&lt;/p&gt;
&lt;p&gt;First, my Docker host kernel version is too low, which caused the error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unknown capability &amp;quot;CAP_AUDIT_READ&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The solution is upgrading kernel. Concourse requires version 3.19+&lt;sup id="fnref:1"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;1&lt;/span&gt;&lt;/sup&gt;. Ubuntu 14.04 can use following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install --install-recommends linux-generic-lts-wily
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second, if Concourse is deployed via docker compose, DNS is changed to loopback address. The job will fail because of DNS lookup error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lookup registry-1.docker.io on 127.0.0.11:53: read udp 127.0.0.1:54286-&amp;gt;127.0.0.11:53: read: connection refused
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just add DNS config based on &lt;a href="https://concourse-ci.org/install.html"&gt;official compose sample&lt;/a&gt; to solve this error. I have configured custom DNS for &lt;code&gt;concourse-web&lt;/code&gt; and &lt;code&gt;concourse-worker&lt;/code&gt;. It is also required to setup a DNS for &lt;code&gt;concourse-worker&lt;/code&gt; using environment variable &lt;code&gt;CONCOURSE_GARDEN_DNS_SERVER&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;2&lt;/span&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;If your cloud server provider has its own DNS servers, log into any host and check &lt;code&gt;/etc/resolv.conf&lt;/code&gt;. Use the DNS servers to replace &lt;code&gt;8.8.8.8&lt;/code&gt; 和 &lt;code&gt;8.8.4.4&lt;/code&gt; in the example below.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;concourse-db:
image: postgres:9.5
environment:
POSTGRES_DB: concourse
POSTGRES_USER: concourse
POSTGRES_PASSWORD: &amp;quot;${POSTGRES_PASSWORD}&amp;quot;
concourse-web:
image: concourse/concourse
links: [concourse-db]
command: web
ports: [&amp;quot;8080:8080&amp;quot;]
volumes: [&amp;quot;keys:/concourse-keys&amp;quot;]
environment:
CONCOURSE_BASIC_AUTH_USERNAME: admin
CONCOURSE_BASIC_AUTH_PASSWORD: &amp;quot;${CONCOURSE_BASIC_AUTH_PASSWORD}&amp;quot;
CONCOURSE_EXTERNAL_URL: &amp;quot;${CONCOURSE_EXTERNAL_URL}&amp;quot;
CONCOURSE_POSTGRES_DATA_SOURCE: &amp;quot;postgres://concourse:${POSTGRES_PASSWORD}@concourse-db:5432/concourse?sslmode=disable&amp;quot;
dns:
- 8.8.8.8
- 8.8.4.4
concourse-worker:
image: concourse/concourse
privileged: true
links: [concourse-web]
command: worker
volumes: [&amp;quot;keys:/concourse-keys&amp;quot;]
environment:
CONCOURSE_TSA_HOST: concourse-web
CONCOURSE_GARDEN_DNS_SERVER: 8.8.8.8
dns:
- 8.8.8.8
- 8.8.4.4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last, Concourse does not pull docker images via the docker daemon in docker host machine. So the config &lt;code&gt;registry-mirror&lt;/code&gt; is ignored. The config must be added in the resource source config&lt;sup id="fnref:3"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;3&lt;/span&gt;&lt;/sup&gt;. It is tedius that cannot be set as a global config.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;jobs:
- name: hello-world
plan:
- task: say-hello
config:
platform: linux
image_resource:
type: docker-image
source:
registry_mirror: https://changeme.mirror.aliyuncs.com
repository: alpine
run:
path: echo
args: [&amp;quot;Hello, world!&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://github.com/concourse/concourse/issues/488#issuecomment-229209912"&gt;unknown capability &amp;ldquo;CAP_AUDIT_READ&amp;rdquo; · Issue #488 · concourse/concourse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://github.com/concourse/bin/issues/18"&gt;[support] guardian inside docker cannot access docker-local DNS · Issue #18 · concourse/bin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="https://github.com/concourse/docker-image-resource/pull/42"&gt;Adding registry mirror parameter on source by gregarcara · Pull Request #42 · concourse/docker-image-resource&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/ci/">CI</category><category domain="https://blog.iany.me/tags/docker/">Docker</category></item><item><title>Convert Web Page to Markdown using Ulysses or Bear</title><link>https://blog.iany.me/2016/11/convert-web-page-to-markdown-using-ulysses-or-bear/</link><pubDate>Sun, 27 Nov 2016 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2016/11/convert-web-page-to-markdown-using-ulysses-or-bear/</guid><description>&lt;details open disabled class="kg-card kg-callout kg-callout-info" data-callout-type="info"&gt;
&lt;summary class="kg-callout-title" tabindex="-1"&gt;
&lt;i aria-hidden="true" class="kg-callout-type fas fa-circle-info"&gt;&lt;/i&gt;
Updates
&lt;/summary&gt;
&lt;div class="kg-callout-content"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2017-05-09&lt;/strong&gt;: The bear current version made clipper easier to use.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Ulysses way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select content in browser and copy.&lt;/li&gt;
&lt;li&gt;Open Ulysses and use menu &lt;code&gt;Edit &amp;gt; Paste from &amp;gt; Rich text&lt;/code&gt; to paste&lt;/li&gt;
&lt;li&gt;Select pasted text and use menu &lt;code&gt;Edit &amp;gt; Copy as &amp;gt; Markdown&lt;/code&gt; to copy&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bear way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy page URL.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://bear.app/faq/X-callback-url%20Scheme%20documentation/#grab-url"&gt;/grab-url&lt;/a&gt; x-callback-url&lt;/li&gt;
&lt;li&gt;After the article is grabbed, use menu &lt;code&gt;Edit &amp;gt; Markdown&lt;/code&gt; to copy&lt;/li&gt;
&lt;/ol&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/macos/">macOS</category><category domain="https://blog.iany.me/tags/markdown/">Markdown</category><category domain="https://blog.iany.me/tags/note-taking/">Note Taking</category></item><item><title>Ctags for Ansible</title><link>https://blog.iany.me/2015/02/ctags-for-ansible/</link><pubDate>Sun, 08 Feb 2015 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2015/02/ctags-for-ansible/</guid><description>&lt;p&gt;Ansible uses YAML to define tasks, playbooks and handlers. If the files follow some conventions, it is easy to index them using &lt;a href="http://ctags.sourceforge.net"&gt;Exuberant Ctags&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, following ctags options will index all the lines starting with &lt;code&gt;- name:&lt;/code&gt; in &lt;code&gt;.yml&lt;/code&gt; and &lt;code&gt;.yaml&lt;/code&gt; files. Save it as &lt;code&gt;.ctags&lt;/code&gt; in the playbook or role top directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--langdef=ansible
--langmap=ansible:.yml.yaml
--regex-ansible=/^[ \t]*-[ \t]*name:[ \t]*(.+)/\1/k,tasks/
--languages=ansible,ruby,python
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is assumed that playbook and task files use extension &lt;code&gt;.yml&lt;/code&gt; or &lt;code&gt;.yaml&lt;/code&gt;. And &lt;code&gt;name&lt;/code&gt; field just follows &lt;code&gt;-&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: name must be the first field just following the dash
hosts: all
roles: [ site ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now generate the tags&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ctags -R .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And try it in vim&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:tselect /keyword
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make it works with &lt;a href="https://github.com/kien/ctrlp.vim"&gt;ctrlp&lt;/a&gt; &lt;code&gt;CtrlPBufTag&lt;/code&gt;, add following config in vimrc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let g:ctrlp_buftag_types = {
\ 'yaml' : '--languages=ansible --ansible-types=k',
\ }
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/ansible/">Ansible</category><category domain="https://blog.iany.me/tags/vim/">Vim</category></item><item><title>Keystroke Sequence Shortcuts in Mac OS X</title><link>https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/</link><pubDate>Mon, 13 Oct 2014 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/</guid><description>&lt;p&gt;It is a headache to find an available keyboard shortcuts in Mac OS X. I used &lt;kbd&gt;Option + Letter&lt;/kbd&gt; and &lt;kbd&gt;Shift + Option + Letter&lt;/kbd&gt; before, since they are preserved for inputting special characters. It has some problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Emacs and terminal require a modifier for &lt;kbd&gt;Meta&lt;/kbd&gt;. I chose &lt;kbd&gt;Command&lt;/kbd&gt;. It means if I want to use application shortcut with &lt;kbd&gt;Command&lt;/kbd&gt; in these applications, such as &lt;kbd&gt;Command+Q&lt;/kbd&gt; to quit the application, I have to use the right one.&lt;/li&gt;
&lt;li&gt;I always forget the shortcuts. Although I have listed them in a sheet, it is a pain to keep it synchronized with the shortcuts defined every where.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I switched to a new solution using keystroke sequence shortcuts recently. All my global shortcuts start with &lt;kbd&gt;Command+M&lt;/kbd&gt; (&lt;kbd&gt;⌘M&lt;/kbd&gt;). A menu is displayed when I typed prefix. If I forget the shortcut, I just need to glance through the menu.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="The shortcuts menu shows all available commands and the corresponding keyboard binding." class="kg-image" loading="lazy" src="https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/cmd-m-menu_hu_2d32e96d1f3ce879.png" srcset="https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/cmd-m-menu_hu_968984963bfb60c5.png 400w, https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/cmd-m-menu_hu_2d32e96d1f3ce879.png 516w" sizes="(max-width: 400px) 100vw, 516px" /&gt;
&lt;figcaption &gt;The shortcuts menu shows all available commands and the corresponding keyboard binding.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="disable-system-default-shortcut"&gt;Disable System Default Shortcut&lt;/h2&gt;
&lt;p&gt;&lt;kbd&gt;⌘M&lt;/kbd&gt; is a system default shortcut to minimize window. I never use it, and it is annoyed when typed by acident. So I disable it and use it as my personal keytroke sequence shortcuts prefix.&lt;/p&gt;
&lt;p&gt;Run the following code&lt;sup id="fnref:1"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;1&lt;/span&gt;&lt;/sup&gt; in terminal to disable it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;defaults write -g NSUserKeyEquivalents -dict-add 'Minimize' '\0'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The changes are applied only in new opened apps. Please reopen all applications, or just restart the system.&lt;/p&gt;
&lt;h2 id="keyboard-maestro"&gt;Keyboard Maestro&lt;/h2&gt;
&lt;p&gt;The shortcuts and the menu in the snapshot are managed by &lt;a href="http://www.keyboardmaestro.com/main/" title="Work Faster with Macros for Mac OS X"&gt;Keyboard Maestro&lt;/a&gt;. There are two ways to implement keystroke sequence in Keyboard Maestro.&lt;/p&gt;
&lt;p&gt;The first solution is setting the same keyboard shortcut for multiple macros. Keyboard Maestro auto detects conflicts and display a menu like the one above. It strips the common prefix, and use the first distinct letter in macro name as the shortcut. Thus, it does not support modifier (like &lt;kbd&gt;Shift&lt;/kbd&gt;, &lt;kbd&gt;Command&lt;/kbd&gt;) in subsequent shortcut. And it requires some trick to name your commands, such as “l) Read Later” and “v) Clipboard History”.&lt;/p&gt;
&lt;p&gt;Since I want to use modifier, I use another solution: a group with the option “Shows a palette for one action when the hot key xxx is pressed”.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Shows a pallete for one action when the hot key xxx is pressed." class="kg-image" loading="lazy" src="https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/cmd-m-macro-group_hu_c7587309e60cc16f.png" srcset="https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/cmd-m-macro-group_hu_4e2ce031b241434b.png 400w, https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/cmd-m-macro-group_hu_44a06795ff395959.png 800w, https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/cmd-m-macro-group_hu_c7587309e60cc16f.png 880w" sizes="(max-width: 800px) 100vw, 880px" /&gt;
&lt;figcaption &gt;Shows a pallete for one action when the hot key xxx is pressed.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It is tricky to display the shortcuts and align them to the right in the menu like in the snapshot. The shortcuts should be added in command name manually and are aligned by inserting tabs and spaces.&lt;/p&gt;
&lt;h2 id="shortcuts-forwarding"&gt;Shortcuts Forwarding&lt;/h2&gt;
&lt;p&gt;Some application provides global shortcuts, but they cannot be used in Keyboard Maestro. The workaround is using a rarely used and hard to type in the application preference, and forward using a simple shortcut through Keyboard Maestro.&lt;/p&gt;
&lt;p&gt;For example, I defined the Alfred 2 clipboard manager shortcut to &lt;kbd&gt;Ctrl+Option+Shift+Command+F4&lt;/kbd&gt;. I use &lt;kbd&gt;Command+M, Command+V&lt;/kbd&gt; (Hold &lt;kbd&gt;Command&lt;/kbd&gt;, type &lt;kbd&gt;M&lt;/kbd&gt;, then &lt;kbd&gt;V&lt;/kbd&gt;, and release &lt;kbd&gt;Command&lt;/kbd&gt;) to show it, because I have a macro in the &lt;kbd&gt;Command+M&lt;/kbd&gt; group which uses the action “Type a Keystroke”.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Send a global shortcut in an application from Keyboard Maestro by sending a keystroke." class="kg-image" loading="lazy" src="https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/shortcut-forward_hu_824686ee9eaba829.png" srcset="https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/shortcut-forward_hu_d1393c134fec25f8.png 400w, https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/shortcut-forward_hu_6a7bb39baf6c94ef.png 800w, https://blog.iany.me/2014/10/mac-keystroke-sequence-shortcuts/shortcut-forward_hu_824686ee9eaba829.png 998w" sizes="(max-width: 800px) 100vw, 998px" /&gt;
&lt;figcaption &gt;Send a global shortcut in an application from Keyboard Maestro by sending a keystroke.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="http://apple.stackexchange.com/a/73957"&gt;http://apple.stackexchange.com/a/73957&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/macos/">macOS</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category></item><item><title>Auto Toggle MacBook Internal Keyboard</title><link>https://blog.iany.me/2014/01/auto-toggle-macbook-internal-keyboard/</link><pubDate>Fri, 17 Jan 2014 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2014/01/auto-toggle-macbook-internal-keyboard/</guid><description>&lt;p&gt;I prefer using external keyboard with my MacBook. When no external monitors are used, a typical setup is placing the keyboard above the internal one, so I can still use the internal touchpad.&lt;/p&gt;
&lt;p&gt;But sometimes the external keyboard may press some keys of the internal keyboard. There is a &lt;a href="http://forums.macrumors.com/showthread.php?t=433407"&gt;solution&lt;/a&gt; to disable the internal keyboard, but it is tedious to run the command manually.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Disable, ignore the warning
sudo kextunload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/
# Enable
sudo kextload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fortunately, &lt;a href="http://www.keyboardmaestro.com/main/"&gt;Keyboard Maestro&lt;/a&gt; supports executing scripts when a USB device is attached or detached.&lt;/p&gt;
&lt;p&gt;I have created two macros to enable and disable internal keyboard.&lt;/p&gt;
&lt;p&gt;Before the macros can be used, you must setup sudoers to allow running the command without password.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo visudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And append following line to the file and save it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;%admin ALL = NOPASSWD: /sbin/kextunload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext, /sbin/kextload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then restart sudo session&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo -K
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/automation/">Automation</category><category domain="https://blog.iany.me/tags/keyboard-maestro/">Keyboard Maestro</category><category domain="https://blog.iany.me/tags/macos/">macOS</category></item><item><title>ActiveRecord includes and preload</title><link>https://blog.iany.me/2013/08/active-record-includes-and-preload/</link><pubDate>Fri, 23 Aug 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/08/active-record-includes-and-preload/</guid><description>&lt;p&gt;&lt;code&gt;ActiveRecord&lt;/code&gt; has two &lt;a href="http://apidock.com/rails/ActiveRecord/QueryMethods"&gt;query methods&lt;/a&gt; to eager load associations, &lt;a href="http://apidock.com/rails/v3.2.13/ActiveRecord/QueryMethods/includes"&gt;includes&lt;/a&gt; and &lt;a href="http://apidock.com/rails/v3.2.13/ActiveRecord/QueryMethods/preload"&gt;preload&lt;/a&gt;. Although the documentation of &lt;code&gt;preload&lt;/code&gt; says&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Allows preloading of args, in the same way that includes does.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Indeed the two methods have some differences.&lt;/p&gt;
&lt;p&gt;In simple words, prefer &lt;code&gt;includes&lt;/code&gt; to eager load associations. Use &lt;code&gt;preload&lt;/code&gt; only when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want to customize &lt;code&gt;select&lt;/code&gt; columns, or&lt;/li&gt;
&lt;li&gt;you meet error &amp;ldquo;Can not eagerly load the polymorphic association&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of the two methods eager load associations to avoid N+1 queries. However, &lt;code&gt;includes&lt;/code&gt; is more aggressive. If it detects that the associations tables are already joined, through explicitly &lt;code&gt;joins&lt;/code&gt; or implicit references in &lt;code&gt;where&lt;/code&gt;, it overwrites &lt;code&gt;select&lt;/code&gt; fields from the joined query and construct result set from it, rather than loading associations in a separate query. See the generated SQL statement below and the fields aliases like &lt;code&gt;t0_r0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;irb(main):049:0&amp;gt; User.includes(:articles).joins(:articles)
SQL (0.7ms) SELECT &amp;quot;users&amp;quot;.&amp;quot;id&amp;quot; AS t0_r0,
&amp;quot;users&amp;quot;.&amp;quot;login&amp;quot; AS t0_r1,
&amp;quot;users&amp;quot;.&amp;quot;name&amp;quot; AS t0_r2,
&amp;quot;users&amp;quot;.&amp;quot;created_at&amp;quot; AS t0_r3,
&amp;quot;users&amp;quot;.&amp;quot;updated_at&amp;quot; AS t0_r4,
&amp;quot;articles&amp;quot;.&amp;quot;id&amp;quot; AS t1_r0,
&amp;quot;articles&amp;quot;.&amp;quot;title&amp;quot; AS t1_r1,
&amp;quot;articles&amp;quot;.&amp;quot;content&amp;quot; AS t1_r2,
&amp;quot;articles&amp;quot;.&amp;quot;user_id&amp;quot; AS t1_r3,
&amp;quot;articles&amp;quot;.&amp;quot;created_at&amp;quot; AS t1_r4,
&amp;quot;articles&amp;quot;.&amp;quot;updated_at&amp;quot; AS t1_r5
FROM &amp;quot;users&amp;quot;
INNER JOIN &amp;quot;articles&amp;quot;
ON &amp;quot;articles&amp;quot;.&amp;quot;user_id&amp;quot; = &amp;quot;users&amp;quot;.&amp;quot;id&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, if the table is not joined yet, &lt;code&gt;includes&lt;/code&gt; will fallback to &lt;code&gt;preload&lt;/code&gt;, which loads association in a separate query. See the SQL in log below that articles are eager loaded in another &lt;code&gt;SELECT&lt;/code&gt; query.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;irb(main):050:0&amp;gt; User.includes(:articles)
User Load (0.1ms) SELECT &amp;quot;users&amp;quot;.* FROM &amp;quot;users&amp;quot;
Article Load (0.2ms) SELECT &amp;quot;articles&amp;quot;.* FROM &amp;quot;articles&amp;quot;
WHERE &amp;quot;articles&amp;quot;.&amp;quot;user_id&amp;quot; IN (1, 2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because &lt;code&gt;includes&lt;/code&gt; may (or may not) overwrite &lt;code&gt;select&lt;/code&gt;, if you have your own &lt;code&gt;select&lt;/code&gt; clause, use &lt;code&gt;preload&lt;/code&gt; instead. See example below:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;users = User.joins(:articles).select(
'users.*, articles.created_at as last_posted_at'
)
users.includes(:articles).first.last_posted_at
# NoMethodError: undefined method `last_posted_at'
users.preload(:articles).first.last_posted_at
# =&amp;gt; &amp;quot;2013-08-23 11:20:36.536968&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Older version of Rails has trouble to &lt;code&gt;includes&lt;/code&gt; polymorphic associations. It seems the newer version of Rails is smart enough to fallback to &lt;code&gt;preload&lt;/code&gt;. However if you still see the error &amp;ldquo;Can not eagerly load the polymorphic association&amp;rdquo;, try switching to &lt;code&gt;preload&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://apidock.com/rails/ActiveRecord/QueryMethods"&gt;ActiveRecord QueryMethods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations"&gt;ActiveRecord Eater Loading Associations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/active-record/">Active Record</category><category domain="https://blog.iany.me/tags/performance/">Performance</category><category domain="https://blog.iany.me/tags/rails/">Rails</category></item><item><title>ActiveRecord uniq, count and distinct</title><link>https://blog.iany.me/2013/07/active-record-uniq-count-and-distinct/</link><pubDate>Tue, 16 Jul 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/07/active-record-uniq-count-and-distinct/</guid><description>&lt;p&gt;&lt;code&gt;ActiveRecord&lt;/code&gt; has two methods to remove duplicates. Method &lt;code&gt;uniq&lt;/code&gt; and option &lt;code&gt;distinct: true&lt;/code&gt; in method &lt;code&gt;count&lt;/code&gt;. I thought &lt;code&gt;uniq.count&lt;/code&gt; and &lt;code&gt;count(distinct: true)&lt;/code&gt; were identical. Indeed, &lt;code&gt;uniq.count&lt;/code&gt; still counts duplicates, and &lt;code&gt;count(distinct: true)&lt;/code&gt; must be used here.&lt;/p&gt;
&lt;p&gt;In simple words, use &lt;code&gt;uniq&lt;/code&gt; to get unique result set, use &lt;code&gt;count(distinct: true)&lt;/code&gt; to count unique result.&lt;/p&gt;
&lt;p&gt;For example, user has many activities, and I want to get all users having a specific type of activities:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;users = User.joins(:activities).where(
activities: { activity_type: 'purchase'}
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because a user may have multiple activities with the same type, the result above may contain duplicate users. Method &lt;code&gt;uniq&lt;/code&gt; can be used here to remove the duplicates:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;users = users.uniq
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But &lt;code&gt;users.uniq.count&lt;/code&gt; generates SQL like below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT DISTINCT COUNT(*) ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This SQL counts all records with duplicates, and apply &lt;code&gt;DISTINCT&lt;/code&gt; on the count, which has only one row. So &lt;code&gt;DISTINCT&lt;/code&gt; has no effect here.&lt;/p&gt;
&lt;p&gt;On the other hand, &lt;code&gt;users.count(distinct: true)&lt;/code&gt; generates SQL below, which removes duplicates first, then count the result.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT COUNT(DISTINCT users.id) ...
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/active-record/">Active Record</category><category domain="https://blog.iany.me/tags/database/">Database</category><category domain="https://blog.iany.me/tags/rails/">Rails</category></item><item><title>Responsive SVG</title><link>https://blog.iany.me/2013/06/responsive-svg/</link><pubDate>Sun, 16 Jun 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/06/responsive-svg/</guid><description>&lt;p&gt;SVG can use percentage as unit, but it is often more convenient to use px as unit. Framework such as d3 also uses px internally. However, it is still easy to scale an SVG using attributes &lt;code&gt;viewBox&lt;/code&gt; and &lt;code&gt;preserveAspectRatio&lt;/code&gt; even px is used as unit internally.&lt;/p&gt;
&lt;h2 id="viewbox-and-preserveaspectratio"&gt;viewBox and preserveAspectRatio&lt;/h2&gt;
&lt;p&gt;The attribute &lt;a href="http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute"&gt;viewBox&lt;/a&gt; specifies viewpoint dimension. SVG can be scaled along with the parent container without changing the internal coordinates.&lt;/p&gt;
&lt;p&gt;For example, the SVG below defines a viewpoint with width 100 and
height 100.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;svg viewBox=&amp;quot;0 0 100 100&amp;quot;&amp;gt;
&amp;lt;circle cx=&amp;quot;50&amp;quot; cy=&amp;quot;50&amp;quot; r=&amp;quot;50&amp;quot; fill=&amp;quot;blue&amp;quot;&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The figure below shows the same SVG in different container sizes: 50x50, 100x50, 100x100, 100x200, 200x200 from left to right.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Same SVG in Different Size Containers" class="kg-image" loading="lazy" src="https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_d41762ac2492bccc.png" srcset="https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_2bebec1cfcce62d.png 400w, https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_56a84172c5222a.png 800w, https://blog.iany.me/2013/06/responsive-svg/same-svg-in-different-containers_hu_d41762ac2492bccc.png 1192w" sizes="(max-width: 800px) 100vw, 1192px" /&gt;
&lt;figcaption &gt;Same SVG in Different Size Containers&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Attribute &lt;a href="http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute"&gt;preserveAspectRatio&lt;/a&gt; controls how SVG is scaled according to parent container. There are many combinations, but the most frequently used are default (when the attribute is not specified, or set to &lt;code&gt;xMidYMid meet&lt;/code&gt; explicitly) and &lt;strong&gt;none&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;default, preserve ratio, align center of SVG to the center of the container, scaled up as much as possible but ensure the entire &lt;code&gt;viewBox&lt;/code&gt; is visible within the container.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;none&lt;/code&gt;, do not preserve ratio, scale SVG to fill the whole container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the figure similar to the previous one but setting &lt;code&gt;preserveAspectRatio&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="`preserveAspectRatio=&amp;#34;none&amp;#34;`" class="kg-image" loading="lazy" src="https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_6c121c8a0a82763a.png" srcset="https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_8fd08d7c8cb873dd.png 400w, https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_669370b9ac619e91.png 800w, https://blog.iany.me/2013/06/responsive-svg/preserveaspectratio-none_hu_6c121c8a0a82763a.png 1186w" sizes="(max-width: 800px) 100vw, 1186px" /&gt;
&lt;figcaption &gt;&lt;code&gt;preserveAspectRatio=&amp;quot;none&amp;quot;&lt;/code&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="responsive-svg"&gt;Responsive SVG&lt;/h2&gt;
&lt;p&gt;First add following CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;svg {
width: 100%;
height: 100%;
}
svg * {
vector-effect: non-scaling-stroke;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first rule ensures SVG always fills parent container. And the second one sets a default &lt;code&gt;vector-effect&lt;/code&gt; that when SVG is scaled, leave stroke width untouched.&lt;/p&gt;
&lt;p&gt;If the SVG does not need to preserve ratio, set &lt;code&gt;preserveAspectRatio&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt;, and set the height of the container. For example, the SVG below has a fixed height &lt;code&gt;50px&lt;/code&gt;, and is half width of the whole page.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;div style=&amp;quot;width:50%;height:50px;&amp;quot;&amp;gt;
&amp;lt;svg viewBox=&amp;quot;0 0 100 100&amp;quot; preserveAspectRatio=&amp;quot;none&amp;quot;&amp;gt;
&amp;lt;circle cx=&amp;quot;50&amp;quot; cy=&amp;quot;50&amp;quot; r=&amp;quot;50&amp;quot; fill=&amp;quot;blue&amp;quot;&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;figure&gt;
&lt;div style="width:50%;height:50px;"&gt;
&lt;svg viewBox="0 0 100 100" style="width:100%;height:100%" preserveAspectRatio="none"&gt;
&lt;circle cx="50" cy="50" r="50" fill="blue" /&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;figcaption&gt;Fixed With, Horizontal scalable&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the SVG must preserve ratio, then we need a bit of JavaScript to change container height when window is resized.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;$.fn.preserveAspectRatio = function() {
this.each(function() {
var $this = $(this);
var ratioSpec = $this.attr('data-preserveAspectRatio').split(':');
var ratio = parseInt(ratioSpec[1], 10) / parseInt(ratioSpec[0], 10);
$this.height($this.width() * ratio);
});
};
var preserveAspectRatio = function() {
$('[data-preserveAspectRatio]').preserveAspectRatio();
};
$(function() {
preserveAspectRatio();
$(window).on('resize', preserveAspectRatio);
// Better to wrap preserveAspectRatio in underscore debounce or jquery-throttle-debounce.
// $(window).on('resize', _.debounce(preserveAspectRatio, 200));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then set &lt;code&gt;data-preserveAspectRatio&lt;/code&gt; on any container that needs preserve aspect ratio. Following example sets the width height ratio to &lt;code&gt;1:1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-html"&gt;&amp;lt;div style=&amp;quot;width:50%;&amp;quot; data-preserveAspectRatio=&amp;quot;1:1&amp;quot;&amp;gt;
&amp;lt;svg viewBox=&amp;quot;0 0 100 100&amp;quot; preserveAspectRatio=&amp;quot;none&amp;quot;&amp;gt;
&amp;lt;circle cx=&amp;quot;50&amp;quot; cy=&amp;quot;50&amp;quot; r=&amp;quot;50&amp;quot; fill=&amp;quot;blue&amp;quot;&amp;gt;
&amp;lt;/svg&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the demo in this &lt;a href="https://codepen.io/doitian/pen/njmzrR"&gt;pen&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20171114160758/http://meloncholy.com/blog/making-responsive-svg-graphs/"&gt;Making responsive SVG graphs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.w3.org/TR/SVG/coords.html"&gt;SVG Coordinate Systems, Transformations and Units&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/css/">CSS</category><category domain="https://blog.iany.me/tags/frontend/">Frontend</category><category domain="https://blog.iany.me/tags/svg/">SVG</category></item><item><title>How Rails Assets Prefix Disables the Session</title><link>https://blog.iany.me/2013/04/how-rails-assets-prefix-disables-the-session/</link><pubDate>Sun, 28 Apr 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/04/how-rails-assets-prefix-disables-the-session/</guid><description>&lt;p&gt;This is original posted on
&lt;a href="https://web.archive.org/web/20151009055627/http://www.intridea.com/blog/2013/3/20/rails-assets-prefix-may-disable-your-session"&gt;intridea blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I recently worked in a Rails project with &lt;a href="https://twitter.com/sporkd"&gt;Peter (@sporkd)&lt;/a&gt;. The
project is intended to be used as a sub-site, and should be served under
sub-URI. After google, we ended up by setting &lt;code&gt;config.assets.prefix&lt;/code&gt; and
wrapped all routes in &lt;code&gt;scope&lt;/code&gt;. The solution is simple and worked well. But
soon, some weird bugs were found, and Peter was successfully isolated the
problem to session (see demo
&lt;a href="https://github.com/sporkd/asset_prefix_test"&gt;sporkd/asset_prefix_test&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;After several hours debugging, I finally found out the cause. To make a long
story short, the routes configured in &lt;code&gt;routes.rb&lt;/code&gt; should not start with
&lt;code&gt;config.assets.prefix&lt;/code&gt;, otherwise session save is skipped. The demo
&lt;code&gt;sporkd/asset_prefix_test&lt;/code&gt; can be fixed by simply setting
&lt;code&gt;config.assets.prefix&lt;/code&gt; to &lt;code&gt;/one/assets&lt;/code&gt;. You also get a bonus by setting a
unique prefix for assets, since it is easy to add expiration header for assets
in web server.&lt;/p&gt;
&lt;h2 id="x-cascade-header-in-rails"&gt;X-Cascade Header in Rails&lt;/h2&gt;
&lt;p&gt;I never knew &lt;code&gt;X-Cascade&lt;/code&gt; header in Rails before. @soulcutter has a
&lt;a href="http://teambandb.typepad.com/soultech/2011/10/x-cascade-header-in-rails.html"&gt;post&lt;/a&gt; that described its usage.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The basic idea is this: if you return a response from a controller with the
X-Cascade header set to &amp;ldquo;pass&amp;rdquo;, it indicates that your controller thinks
something else should handle the request. So rails (or is it rack? in any
case&amp;hellip;) will continue down your routes looking for the next rule that
matches the request.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Indeed, &lt;code&gt;X-Cascade&lt;/code&gt; is not only restricted in controller, if a mounted engine
sets this header, Rails also continues down the routes searching.&lt;/p&gt;
&lt;p&gt;It is a feature of Rails. Since 3.2, Rails has moved the routes logic to
&lt;a href="https://github.com/rails/journey"&gt;journey&lt;/a&gt;. The &lt;code&gt;X-Cascade&lt;/code&gt; trick can be found in
&lt;a href="https://github.com/rails/journey/blob/master/lib/journey/router.rb#L69"&gt;journey/router.rb#L69&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pay attention that, the rack &lt;code&gt;env&lt;/code&gt; object is shared when request is passed
on. So if &lt;code&gt;env&lt;/code&gt; is changed by former route, the latter one is affected. This
is the root cause of the weird session issue, because session is controlled by
&lt;code&gt;env['rack.session.options']&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="sprockets-who-skips-the-session"&gt;Sprockets, who skips the session&lt;/h2&gt;
&lt;p&gt;Sprockets, the gem for rails assets pipeline, mounts itself on
&lt;code&gt;config.assets.prefix&lt;/code&gt; and &lt;a href="https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/sprockets/bootstrap.rb#L27"&gt;prepends&lt;/a&gt;
the route to Rails. So if user accesses a page which path starts with
&lt;code&gt;config.assets.prefix&lt;/code&gt;, sprockets always processes the request first.&lt;/p&gt;
&lt;p&gt;Maybe for performance, sprockets disables session save by changing
&lt;code&gt;env['rack.session.options']&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;env['rack.session.options'] ||= {}
env['rack.session.options'][:defer] = true
env['rack.session.options'][:skip] = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The options are changed even when asset is not found. If so, sprockets
returns 404 and sets the header &lt;code&gt;X-Cascade&lt;/code&gt;. Then Rails passes the request to
controller, and correct page is rendered as expected. However, since the
session is already disabled by sprockets, the changed session in controller is
never saved.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;env&lt;/code&gt; is a shared resource between routes when &lt;code&gt;X-Cascade&lt;/code&gt; is set, it
should not be changed unless it has to. When asset is not found, sprockets
should just pass though without touching &lt;code&gt;env&lt;/code&gt;, so I submit a
&lt;a href="https://github.com/sstephenson/sprockets/pull/421"&gt;PR&lt;/a&gt; for it.&lt;/p&gt;
&lt;h2 id="how-we-debug"&gt;How we Debug&lt;/h2&gt;
&lt;p&gt;Peter and I worked in different time zones. He first found the session issue
because several features related to session did not work. He made the demo
&lt;code&gt;sporkd/asset_prefix_test&lt;/code&gt; to isolate the issue using minimum code at the end
of the day in his time zone and left me the message.&lt;/p&gt;
&lt;p&gt;When my day started, I got the message and started debugging on session based on
the demo in
&lt;a href="https://github.com/doitian/asset_prefix_test/compare/asset-prefix-one-deep"&gt;doitian/asset_prefix_test&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because session store class is customizable, I inherited one from default
cookie store and added breakpoints using &lt;a href="https://github.com/pry/pry"&gt;pry&lt;/a&gt;. Soon I found out that
&lt;code&gt;options[:skip]&lt;/code&gt; was &lt;code&gt;true&lt;/code&gt;, but I had no idea where it was set to
&lt;code&gt;true&lt;/code&gt;. Then I did a grep (using &lt;a href="https://github.com/ggreer/the_silver_searcher"&gt;ag&lt;/a&gt;) in all gems, and fortunately, only
sprockets has set this option to &lt;code&gt;true&lt;/code&gt;. The remaining work was just figuring
out why sprockets is invoked before controller action.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/rails/">Rails</category><category domain="https://blog.iany.me/tags/session/">Session</category></item><item><title>Select multiple zsh completions item</title><link>https://blog.iany.me/2013/04/select-multiple-zsh-completions-item/</link><pubDate>Mon, 08 Apr 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/04/select-multiple-zsh-completions-item/</guid><description>&lt;pre&gt;&lt;code class="language-zsh"&gt;# ^Xa to insert all matches
zle -C all-matches complete-word _generic
bindkey '^Xa' all-matches
zstyle ':completion:all-matches:*' old-matches only
zstyle ':completion:all-matches::::' completer _all_matches
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;UPDATE: The snippet above brings &lt;code&gt;insert-completions&lt;/code&gt; to zsh. Thanks, Trevor Joynson, who has provided the solution in the comment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Command &lt;code&gt;insert-completions&lt;/code&gt; (see &lt;a href="http://linux.die.net/man/1/bash"&gt;man bash&lt;/a&gt;) is the
feature I most miss in bash.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[insert-completions] Insert all completions of the text before point that would have been
generated by possible-completions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Its default binding is &lt;kbd&gt;M-*&lt;/kbd&gt;, a.k.a, &lt;kbd&gt;Alt-Shift-8&lt;/kbd&gt; or
&lt;kbd&gt;Meta-Shift-8&lt;/kbd&gt;.&lt;/p&gt;
&lt;p&gt;It is useful for simple batch operations, such as removing all git branches
starting with &lt;code&gt;feature/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After I switched to zsh, I have been looking for a similar command, but for
no results. However, I just discovered the zsh way to select multiple
completions recently, using &lt;code&gt;accept-and-hold&lt;/code&gt; (see
&lt;a href="http://linux.die.net/man/1/zshzle"&gt;man zshzle&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[accept-and-hold] Push the contents of the buffer on the buffer stack and
execute it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The default binding is &lt;kbd&gt;M-a&lt;/kbd&gt; (a.k.a, &lt;kbd&gt;Alt-a&lt;/kbd&gt; or
&lt;kbd&gt;Meta-a&lt;/kbd&gt;). If the command line has input content &lt;code&gt;ls&lt;/code&gt;, &lt;kbd&gt;M-a&lt;/kbd&gt;
executes the line &lt;code&gt;ls&lt;/code&gt; without clearing the input.&lt;/p&gt;
&lt;p&gt;It seems unrelated. But if completion menu is activated and a menu item is
selected (the simplest way is &lt;kbd&gt;Tab&lt;/kbd&gt; twice), &lt;code&gt;accept-and-hold&lt;/code&gt; accepts
the current menu item without closing completing menu. So it is possible to
select multiple items by navigating to wanted items, and type &lt;kbd&gt;M-a&lt;/kbd&gt;
to select them.&lt;/p&gt;
&lt;h2 id="bash-sample-to-delete-git-branches"&gt;Bash Sample to delete git branches&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First enter following text.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feature/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;kbd&gt;Tab&lt;/kbd&gt; to activate completion.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feature/
feature/a feature/b
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;kbd&gt;M-*&lt;/kbd&gt; to insert all completions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feature/a feature/b
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Review, delete branches should be keep.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;kbd&gt;Enter&lt;/kbd&gt; to execute the line.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="zsh-way"&gt;Zsh way&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First enter following text.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feature/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;kbd&gt;Tab&lt;/kbd&gt; to activate completion.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feature/
feature/a feature/b
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;kbd&gt;Tab&lt;/kbd&gt; again, to select first item. Brackets demonstrate
which item is currently selected.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feature/a
[feature/a] feature/b
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;kbd&gt;M-a&lt;/kbd&gt; to keep selected item and continue completing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git branch -D feature/a feature/b
feature/a [feature/b]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;kbd&gt;Tab&lt;/kbd&gt; to skip branches that should be keep. The item selection
mode also supports &lt;kbd&gt;C-n&lt;/kbd&gt;, &lt;kbd&gt;C-p&lt;/kbd&gt;, &lt;kbd&gt;C-f&lt;/kbd&gt;,
&lt;kbd&gt;C-b&lt;/kbd&gt; to move around, and &lt;kbd&gt;C-s&lt;/kbd&gt;, &lt;kbd&gt;C-r&lt;/kbd&gt; to
search.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When cursor is on the last branch to be deleted, type &lt;kbd&gt;Enter&lt;/kbd&gt; to
accept it and stop completion. If &lt;kbd&gt;M-a&lt;/kbd&gt; is typed by accident, you
have to delete the last inserted completion item, because zsh auto inserts
current selected item.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="discover-zsh-commands"&gt;Discover zsh commands&lt;/h2&gt;
&lt;p&gt;The builtin &lt;code&gt;bindkey&lt;/code&gt; lists all commands with key-bindings. They are most
frequently used commands, that&amp;rsquo;s why they deserve a key-binding.&lt;/p&gt;
&lt;p&gt;The commands reference can be looked up in &lt;code&gt;man zshzle&lt;/code&gt;. I also discovered
several other interesting key-bindings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-h&lt;/code&gt; Run &lt;code&gt;man&lt;/code&gt; on current entered command. Say you are editing &lt;code&gt;tcpdump&lt;/code&gt;
options, and want to check manual without losing the input.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-?&lt;/code&gt; Run &lt;code&gt;which-command&lt;/code&gt; on current entered command.&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/console/">Console</category><category domain="https://blog.iany.me/tags/zsh/">Zsh</category></item><item><title>A trick to use just jasmine gem to test Javascript in Rails</title><link>https://blog.iany.me/2013/01/rails-javascript-test-with-jasmine-gem/</link><pubDate>Thu, 31 Jan 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/01/rails-javascript-test-with-jasmine-gem/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://jasmine.github.io/"&gt;Jasmine&lt;/a&gt; is a behavior-driven development framework for testing JavaScript
code. It does not depend on any other JavaScript frameworks. It does not
require a DOM. And it has a clean, obvious syntax so that you can easily write
tests.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is very easy to integrate Jasmine into Rails, since the team provides the &lt;a href="https://github.com/jasmine/jasmine-gem" title="jasmine-gem"&gt;jasmine gem&lt;/a&gt;. The jasmine gem also supports assets pipeline, just prepend &lt;code&gt;assets/&lt;/code&gt; to file path. For example, &lt;code&gt;app/assets/javascripts/application.js.coffee&lt;/code&gt; can be referred as &lt;code&gt;assets/application.js&lt;/code&gt; in &lt;code&gt;jasmine.yml&lt;/code&gt; &lt;code&gt;src_files&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;spec_files&lt;/code&gt; does not support assets pipeline, so the files in &lt;code&gt;spec/javascripts&lt;/code&gt; cannot be written in CoffeeScript. But if &lt;code&gt;spec/javascripts&lt;/code&gt; is appended to assets pipeline paths, the spec files can be added to &lt;code&gt;src_files&lt;/code&gt; in jasmine.yml.&lt;/p&gt;
&lt;p&gt;This post explains how to apply such trick, and how to integrate jasmine gem with &lt;a href="https://github.com/netzpirat/guard-jasmine" title="netzpirat/guard-jasmine"&gt;guard-jasmine&lt;/a&gt; and &lt;a href="https://travis-ci.org/" title="Travis CI"&gt;travis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also created a demo repository, see its &lt;a href="https://github.com/doitian/rails-jasmine-demo/commits/master"&gt;commits&lt;/a&gt; for integration steps.&lt;/p&gt;
&lt;h2 id="jasmine-integration"&gt;Jasmine Integration&lt;/h2&gt;
&lt;p&gt;Jasmine 1.3.1 &lt;a href="https://github.com/pivotal/jasmine-gem/issues/120"&gt;removes&lt;/a&gt; the ability to load &lt;code&gt;jasmine_config.rb&lt;/code&gt; before executing specs. Although it is possible to load the file by &lt;a href="https://gist.github.com/doitian/20c35dd980634d2fc7a72bbda573843a" title="Load jasmine_config.rb in jasmine 1.3.1"&gt;adding a rake dependency&lt;/a&gt;, but the trick does not work for &lt;code&gt;rake jasmine:ci&lt;/code&gt;. So please use Jasmine 1.3.0 until a new version is released.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;First add jasmine in Gemfile&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;group :development, :test do
gem &amp;quot;jasmine&amp;quot;, &amp;quot;1.3.0&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and run &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then generate config files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rails g jasmine:install
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Append &lt;code&gt;spec/javascripts&lt;/code&gt; to assets paths by creating file
&lt;code&gt;spec/javascripts/support/jasmine_config.rb&lt;/code&gt; and add following line to the
file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;Rails.application.assets.append_path File.expand_path('../..', __FILE__)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit &lt;code&gt;spec/javascripts/support/jasmine.yml&lt;/code&gt;. Append &lt;code&gt;assets/specs.js&lt;/code&gt; to
&lt;code&gt;src_files&lt;/code&gt;. Also set &lt;code&gt;spec_files&lt;/code&gt; and &lt;code&gt;helpers&lt;/code&gt; to &lt;code&gt;[]&lt;/code&gt;, otherwise the
JavaScript files may be included twice.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;src_files:
- assets/application.js
- assets/specs.js
stylesheets:
- stylesheets/**/*.css
helpers: []
spec_files: []
src_dir:
spec_dir:
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the file &lt;code&gt;spec/javascripts/specs.js&lt;/code&gt;. Usually, it just needs include
all the js files in directory &lt;code&gt;spec/javascripts&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;// Ensure helpers are loaded first. Remove the following line if
// helpers directory is not created yet.
//= require_tree ./helpers
//= require_tree ./
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a spec file to test the integration, e.g.,
&lt;code&gt;spec/javascripts/foobar_spec.js.coffee&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-coffeescript"&gt;# spec/javascripts/foobar_spec.js.coffee
describe &amp;quot;foobar&amp;quot;, -&amp;gt;
it 'works', -&amp;gt; expect(1 + 1).toEqual(2);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start jasmine server by &lt;code&gt;rake jasmine&lt;/code&gt; and visit the test page
&lt;span&gt;http://localhost:8888&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="guard"&gt;Guard&lt;/h2&gt;
&lt;p&gt;The defult config of &lt;a href="https://github.com/netzpirat/guard-jasmine" title="netzpirat/guard-jasmine"&gt;guard-jasmine&lt;/a&gt; is intended to be used with &lt;a href="https://github.com/bradphelan/jasminerice" title="bradphelan/jasminerice"&gt;jasminerice&lt;/a&gt;. But it also allows starting jasmine gem server as well.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Add guard and guard-jasmine in Gemfile.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;group :development do
gem 'guard'
gem 'guard-jasmine'
end
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;bundle install&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add following jasmine guard config in Guardfile&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;require 'guard/jasmine'
port = ::Guard::Jasmine.find_free_server_port
guard :jasmine, :server =&amp;gt; :jasmine_gem, :port =&amp;gt; port, :jasmine_url =&amp;gt; &amp;quot;http://localhost:#{port}/&amp;quot; do
watch(%r{spec/javascripts/support/.+\.(?:rb|yml)$}) { 'spec/javascripts' }
watch(%r{spec/javascripts/helpers/.+\.rb$}) { 'spec/javascripts' }
watch(%r{spec/javascripts/spec\.(?:js\.coffee|js|coffee)$}) { 'spec/javascripts' }
watch(%r{spec/javascripts/.+_spec\.(?:js\.coffee|js|coffee)$})
watch(%r{app/assets/javascripts/(.+?)\.(js\.coffee|js|coffee)(?:\.\w+)*$}) { |m| &amp;quot;spec/javascripts/#{ m[1] }_spec.#{ m[2] }&amp;quot; }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now &lt;code&gt;guard-jasmine&lt;/code&gt; will start the server provided by &lt;code&gt;jasmine-gem&lt;/code&gt; and visit the test page using &lt;a href="http://phantomjs.org/" title="PhantomJS: Headless WebKit with JavaScript API"&gt;phantomjs&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="ci"&gt;CI&lt;/h2&gt;
&lt;p&gt;The jasmine gem has another task &lt;code&gt;rake jasmine:ci&lt;/code&gt; for continuous integration environments. To run the test on server without GUI, &lt;code&gt;xvfb&lt;/code&gt; can be used.&lt;/p&gt;
&lt;p&gt;See the following &lt;code&gt;.travis.yml&lt;/code&gt;, in &lt;code&gt;before_install&lt;/code&gt;, &lt;code&gt;xvfb&lt;/code&gt; is started and &lt;code&gt;DISPLAY&lt;/code&gt; is set so GUI applications know where to render their window.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;language: ruby
script: bundle exec rake jasmine:ci
before_install:
- &amp;quot;/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16&amp;quot;
- &amp;quot;export DISPLAY=:99.0&amp;quot;
- &amp;quot;export JASMINE_BROWSER=firefox&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jasmine/jasmine-gem" title="jasmine-gem"&gt;jasmine gem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://railscasts.com/episodes/261-testing-javascript-with-jasmine" title="#261 Testing JavaScript with Jasmine - RailsCasts"&gt;#261 Testing JavaScript with Jasmine - RailsCasts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://railscasts.com/episodes/261-testing-javascript-with-jasmine-revised" title="#261 Testing JavaScript with Jasmine (revised) - RailsCasts"&gt;#261 Testing JavaScript with Jasmine (revised) - RailsCasts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://about.travis-ci.org/docs/user/gui-and-headless-browsers/" title="Travis CI: GUI &amp;amp; Headless browser testing on travis-ci.org"&gt;Travis CI: GUI &amp;amp; Headless browser testing on travis-ci.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/javascript/">JavaScript</category><category domain="https://blog.iany.me/tags/rails/">Rails</category><category domain="https://blog.iany.me/tags/software-test/">Software Test</category></item><item><title>Rails Compound Input</title><link>https://blog.iany.me/2013/01/rails-compound-input/</link><pubDate>Sat, 19 Jan 2013 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2013/01/rails-compound-input/</guid><description>&lt;p&gt;When I implement
&lt;a href="https://github.com/19wu/19wu/pull/160"&gt;time input feature&lt;/a&gt; for
&lt;a href="https://github.com/saberma/19wu"&gt;19wu&lt;/a&gt; (an open source ticket sale system), I
want to split the datetime into date and time parts, so JavaScript date picker
and time picker can be used. This post introduces two methods I found.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Compound datetime input" class="kg-image" loading="lazy" src="https://blog.iany.me/2013/01/rails-compound-input/compound_datetime_input_hu_294b76325e647193.png" /&gt;
&lt;figcaption &gt;Compound datetime input&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;composed_of&lt;/code&gt; utilizes &lt;code&gt;assign_multiparameter_attributes&lt;/code&gt; trick like
&lt;code&gt;datetime_select&lt;/code&gt;, and &lt;code&gt;fields_for&lt;/code&gt; mocks an association.&lt;/p&gt;
&lt;p&gt;The github repository &lt;a href="https://github.com/doitian/rails-compound-input-demo"&gt;doitian/rails-compound-input-demo&lt;/a&gt; contains demos for both methods.&lt;/p&gt;
&lt;h2 id="composed_of"&gt;composed_of&lt;/h2&gt;
&lt;p&gt;Rails has built-in compound inputs &lt;code&gt;date_select&lt;/code&gt;, &lt;code&gt;time_select&lt;/code&gt; and
&lt;code&gt;datetime_select&lt;/code&gt;. They use trick that if parameter name has parentheses, they
will be used in the attribute class constructor (see
&lt;a href="http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes" title="ActiveRecord::AttributeAssignment#assign_multiparameter_attributes"&gt;assign_multiparameter_attributes&lt;/a&gt;). However, datetime attribute is of type
DateTime, which accepts 6 parameters year, month, day, hour, minute and
second.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html#method-i-composed_of" title="ActiveRecord::Aggregations.composed_of"&gt;composed_of&lt;/a&gt; can be used here to represent the datetime attribute using
value object, which class constructor accepts date and time strings. See
&lt;code&gt;CompoundDatetime#initialize&lt;/code&gt; below.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/models/compound_datetime.rb"&gt;compound_datetime.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class CompoundDatetime
def self.from_datetime(datetime)
new.tap do |result|
result.datetime = datetime
end
end
attr_accessor :datetime
# Accepts date and time string. The form just need to submit params
#
# - compound_beginning_time(1s) for date
# - compound_beginning_time(2s) for time
def initialize(date = nil, time = nil)
if date.present?
@datetime = Time.zone.parse([date.presence, time.presence || ''].join(' '))
end
end
def date
@datetime.strftime('%Y-%m-%d') if @datetime
end
def time
@datetime.strftime('%H:%M') if @datetime
end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then setup the mapping in model &lt;code&gt;Event&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/models/event.rb"&gt;event.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Event &amp;lt; ActiveRecord::Base
attr_accessible :beginning_time, :title
attr_accessible :compound_beginning_time
composed_of :compound_beginning_time, {
:class_name =&amp;gt; 'CompoundDatetime',
:mapping =&amp;gt; [ %w(beginning_time datetime) ],
:converter =&amp;gt; Proc.new { |datetime| CompoundDatetime.from_datetime(datetime) }
}
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The form view just needs set correct name:&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/views/events/_form.html.erb"&gt;events/_form.html.erb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&amp;quot;field&amp;quot;&amp;gt;
&amp;lt;%= f.label :compound_beginning_time, 'Begining Time' %&amp;gt;&amp;lt;BR /&amp;gt;
&amp;lt;%= text_field_tag 'event[compound_beginning_time(1s)]', @event.compound_beginning_time.date, :placeholder =&amp;gt; 'yyyy-mm-dd' %&amp;gt;
&amp;lt;%= text_field_tag 'event[compound_beginning_time(2s)]', @event.compound_beginning_time.time, :placeholder =&amp;gt; 'HH:MM' %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="fields_for"&gt;fields_for&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fields_for&lt;/code&gt; is usually used to embed associations in form. However, all it
required was a method to return the named attribute, and then a &lt;code&gt;&amp;lt;field&amp;gt;_attributes=&lt;/code&gt;
writer to interpret the hash on the other side. See the section &amp;ldquo;Nested Attributes Examples&amp;rdquo; in the
&lt;a href="https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for"&gt;&lt;code&gt;fields_for&lt;/code&gt; API document&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First create &lt;code&gt;CompoundDatetime&lt;/code&gt; class which exposes date and time
fields. &lt;code&gt;assign_attributes&lt;/code&gt; handles the hash params passed from form. Method
&lt;code&gt;persisted?&lt;/code&gt; is required to quiet &lt;code&gt;NoMethodError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/models/compound_datetime.rb"&gt;compound_datetime.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class CompoundDatetime
attr_accessor :datetime
def initialize(datetime)
@datetime = datetime
end
# accepts hash like:
#
# {
# 'date' =&amp;gt; '2012-12-20',
# 'time' =&amp;gt; '20:30'
# }
def assign_attributes(hash)
if hash[:date].present?
@datetime = Time.zone.parse([hash[:date].presence, hash[:time].presence || ''].join(' '))
end
self
end
def date
@datetime.strftime('%Y-%m-%d') if @datetime
end
def time
@datetime.strftime('%H:%M') if @datetime
end
def persisted?; false; end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model just delegates the named attribute and &lt;code&gt;&amp;lt;field&amp;gt;_attributes=&lt;/code&gt; method to &lt;code&gt;CompoundDatetime&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/models/event.rb"&gt;event.rb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ruby"&gt;class Event &amp;lt; ActiveRecord::Base
attr_accessible :beginning_time, :title
attr_accessible :compound_beginning_time_attributes
def compound_beginning_time
CompoundDatetime.new(beginning_time)
end
def compound_beginning_time_attributes=(attributes)
self.beginning_time = compound_beginning_time.assign_attributes(attributes).datetime
end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The form view uses &lt;code&gt;fields_for&lt;/code&gt; helper to nest fields of &lt;code&gt;compound_begining_time&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/views/events/_form.html.erb"&gt;events/_form.html.erb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&amp;quot;field&amp;quot;&amp;gt;
&amp;lt;%= f.label :beginning_time %&amp;gt;&amp;lt;br /&amp;gt;
&amp;lt;%= f.fields_for :compound_beginning_time do |fields| %&amp;gt;
&amp;lt;%= fields.text_field :date, :placeholder =&amp;gt; 'yyyy-mm-dd' %&amp;gt;
&amp;lt;%= fields.text_field :time, :placeholder =&amp;gt; 'HH:MM' %&amp;gt;
&amp;lt;% end %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="reference"&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.alexrothenberg.com/2009/05/21/how-to-use-dates-in-rails-when-your.html"&gt;Alex Rothenberg - How to use dates in Rails when your database stores a string&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for"&gt;&lt;code&gt;fields_for&lt;/code&gt; API document&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://guides.rubyonrails.org/form_helpers.html"&gt;Ruby on Rails Guides: Rails Form helpers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes" title="ActiveRecord::AttributeAssignment#assign_multiparameter_attributes"&gt;assign_multiparameter_attributes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html#method-i-composed_of" title="ActiveRecord::Aggregations.composed_of"&gt;composed_of&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/form/">Form</category><category domain="https://blog.iany.me/tags/frontend/">Frontend</category><category domain="https://blog.iany.me/tags/rails/">Rails</category></item><item><title>Fix TAB Binding For yasnippet And auto-complete</title><link>https://blog.iany.me/2012/03/fix-tab-binding-for-yasnippet-and-auto-complete/</link><pubDate>Sat, 31 Mar 2012 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2012/03/fix-tab-binding-for-yasnippet-and-auto-complete/</guid><description>&lt;p&gt;There are two TAB&amp;rsquo;s in Emacs, &lt;code&gt;(kbd &amp;quot;TAB&amp;quot;)&lt;/code&gt; (&lt;code&gt;\t&lt;/code&gt;, &lt;code&gt;[9]&lt;/code&gt;) and &lt;code&gt;(kbd &amp;quot;&amp;lt;tab&amp;gt;&amp;quot;)&lt;/code&gt; (&lt;code&gt;[tab]&lt;/code&gt;). If modes like &lt;a href="https://github.com/joaotavora/yasnippet"&gt;yasnippet&lt;/a&gt; and &lt;a href="https://github.com/auto-complete/auto-complete"&gt;auto-complete&lt;/a&gt; want to bind on &lt;code&gt;TAB&lt;/code&gt;, their trigger key must be the same with the original Tab command. Since Emacs binds &lt;code&gt;indent-for-tab-command&lt;/code&gt; on &lt;code&gt;(kbd &amp;quot;TAB&amp;quot;)&lt;/code&gt;, so it&amp;rsquo;s better to use it as the trigger key. Yasnippet binds to it by default, It is also easy to setup &lt;code&gt;auto-complete&lt;/code&gt; to trigger using Tab.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-lisp"&gt;;; trigger using TAB and disable auto start
(custom-set-variables
'(ac-trigger-key &amp;quot;TAB&amp;quot;)
'(ac-auto-start nil)
'(ac-use-menu-map t))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But in some modes (&lt;code&gt;ruby-mode&lt;/code&gt;, &lt;code&gt;markdown-mode&lt;/code&gt;, &lt;code&gt;org-mode&lt;/code&gt;), the command is bind to &lt;code&gt;(kbd &amp;quot;&amp;lt;tab&amp;gt;&amp;quot;)&lt;/code&gt;, when the real Tab key is typed, the function bind on &lt;code&gt;(kbd &amp;quot;&amp;lt;tab&amp;gt;)&lt;/code&gt; has higher priority, so yasnippet and auto-complete are not invoked. It is easy to fix by moving the keybinding:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-lisp"&gt;(defun iy-tab-noconflict ()
(let ((command (key-binding [tab]))) ; remember command
(local-unset-key [tab]) ; unset from (kbd &amp;quot;&amp;lt;tab&amp;gt;&amp;quot;)
(local-set-key (kbd &amp;quot;TAB&amp;quot;) command))) ; bind to (kbd &amp;quot;TAB&amp;quot;)
(add-hook 'ruby-mode-hook 'iy-ac-tab-noconflict)
(add-hook 'markdown-mode-hook 'iy-ac-tab-noconflict)
(add-hook 'org-mode-hook 'iy-ac-tab-noconflict)
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/emacs/">Emacs</category></item><item><title>Use Popup isearch For Yasnippet Prompt</title><link>https://blog.iany.me/2012/03/use-popup-isearch-for-yasnippet-prompt/</link><pubDate>Fri, 30 Mar 2012 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2012/03/use-popup-isearch-for-yasnippet-prompt/</guid><description>&lt;p&gt;&lt;a href="https://github.com/joaotavora/yasnippet"&gt;Yasnippet&lt;/a&gt; tries functions in &lt;code&gt;yas/prompt-functions&lt;/code&gt; when it needs user to select one choice, such as selecting snippets with the same trigger key, such as helper method &lt;code&gt;yas/choose-value&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/auto-complete/popup-el"&gt;popup&lt;/a&gt; is a visual popup interface library extracted from &lt;a href="https://github.com/auto-complete/auto-complete"&gt;auto-complete&lt;/a&gt; by its author. It has better look and feel than all the built-in &lt;code&gt;yas/prompt-functions&lt;/code&gt;. Also it is easy to customize, and its isearch mode is very efficient, the items are filtered on-the-fly when typing.&lt;/p&gt;
&lt;figure class="kg-card kg-gallery-card kg-width-wide"&gt;
&lt;div class="kg-gallery-container"&gt;
&lt;div class="kg-gallery-row"&gt;
&lt;div class="kg-gallery-image"&gt;&lt;img alt="Popup snapshot: choices" class="tippy" loading="lazy" width="273" height="166" src="https://blog.iany.me/2012/03/use-popup-isearch-for-yasnippet-prompt/choises_hu_f31d4431d0059d2d.png" data-tippy-content="Popup snapshot: choices" /&gt;&lt;/div&gt;
&lt;div class="kg-gallery-image"&gt;&lt;img alt="Popup snapshot: filter" class="tippy" loading="lazy" width="273" height="104" src="https://blog.iany.me/2012/03/use-popup-isearch-for-yasnippet-prompt/filter_by_keyword_hu_d1cdbe0e3f825240.png" data-tippy-content="Popup snapshot: filter" /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The integration is easy. Load &lt;code&gt;popup.el&lt;/code&gt;, implement one prompt function and
add it to &lt;code&gt;yas/prompt-functions&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://gist.github.com/doitian/2245733"&gt;yasnippet-popup-isearch-prompt.el&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-lisp"&gt;(require 'popup)
(require 'yasnippet)
;; add some shotcuts in popup menu mode
(define-key popup-menu-keymap (kbd &amp;quot;M-n&amp;quot;) 'popup-next)
(define-key popup-menu-keymap (kbd &amp;quot;TAB&amp;quot;) 'popup-next)
(define-key popup-menu-keymap (kbd &amp;quot;&amp;lt;tab&amp;gt;&amp;quot;) 'popup-next)
(define-key popup-menu-keymap (kbd &amp;quot;&amp;lt;backtab&amp;gt;&amp;quot;) 'popup-previous)
(define-key popup-menu-keymap (kbd &amp;quot;M-p&amp;quot;) 'popup-previous)
(defun yas/popup-isearch-prompt (prompt choices &amp;amp;optional display-fn)
(when (featurep 'popup)
(popup-menu*
(mapcar
(lambda (choice)
(popup-make-item
(or (and display-fn (funcall display-fn choice))
choice)
:value choice))
choices)
:prompt prompt
;; start isearch mode immediately
:isearch t
)))
(setq yas/prompt-functions '(yas/popup-isearch-prompt yas/no-prompt))
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/emacs/">Emacs</category></item><item><title>CSS Line Wrap Indicator</title><link>https://blog.iany.me/2012/02/css-line-wrap-indicator/</link><pubDate>Sat, 04 Feb 2012 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2012/02/css-line-wrap-indicator/</guid><description>&lt;p&gt;Many editor can wrap a line that reaches the window width and show an indicator in the margin, for example, Emacs.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Emacs line wrap with bent arrow in fringe" class="kg-image" loading="lazy" src="https://blog.iany.me/2012/02/css-line-wrap-indicator/emacs_hu_21617bd44e47069e.png" srcset="https://blog.iany.me/2012/02/css-line-wrap-indicator/emacs_hu_5d8ba910cd98aab5.png 400w, https://blog.iany.me/2012/02/css-line-wrap-indicator/emacs_hu_21617bd44e47069e.png 476w" sizes="(max-width: 400px) 100vw, 476px" /&gt;
&lt;figcaption &gt;Emacs line wrap with bent arrow in fringe&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I want to add such line wrap indicators to the code block in HTML. It is easy to add indicators as background image in CSS. But the indicators should only be shown when the line is wrapped. And from the figure, the wrap indicator is not displayed on the last line in the right margin, and not on the first line in the left margin. There&amp;rsquo;s a &lt;code&gt;:first-line&lt;/code&gt; pseudo element selector, there&amp;rsquo;s no &lt;code&gt;:last-line&lt;/code&gt;. However, it can be achieved by using &lt;code&gt;:before&lt;/code&gt; and &lt;code&gt;:after&lt;/code&gt; with position trick.&lt;/p&gt;
&lt;p&gt;You can check the result in this &lt;a href="https://jsfiddle.net/doitian/97bfwx6q/5/"&gt;jsfiddle&lt;/a&gt;. Resize the result panel to see the line wrap indicators. Following is a detailed explanation.&lt;/p&gt;
&lt;h2 id="add-line-markup"&gt;Add Line Markup&lt;/h2&gt;
&lt;p&gt;First, the code block in pre must be split by lines and add markup so that CSS can be applied.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;(font-lock-add-keywords
'ruby-mode
'((&amp;quot;\\(\\b\\sw[_a-zA-Z0-9]*:\\)\\(?:\\s-\\|$\\)&amp;quot; (1 font-lock-constant-face))))
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is converted to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;lt;span class=&amp;quot;line&amp;quot;&amp;gt;(font-lock-add-keywords
&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;line&amp;quot;&amp;gt; 'ruby-mode
&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;line&amp;quot;&amp;gt; '((&amp;quot;\\(\\b\\sw[_a-zA-Z0-9]*:\\)\\(?:\\s-\\|$\\)&amp;quot; (1 font-lock-constant-face))))
&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is easy to do such conversion by replace new line character &lt;code&gt;\n&lt;/code&gt; to &lt;code&gt;\n&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;line&amp;quot;&amp;gt;&lt;/code&gt; and then wrap with first open tag and the last close tag.&lt;/p&gt;
&lt;p&gt;Because I want to show indicators in left, right padding of &lt;code&gt;span.line&lt;/code&gt;, it should be displayed as block to expand horizontally. Also enable line wrap on &lt;code&gt;pre&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;pre {
white-space: pre-wrap;
}
pre code, span.line {
display: block;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pay attention to the new line position. It is included in the added
&lt;code&gt;span.line&lt;/code&gt; elements. Otherwise extra empty line is added between lines.&lt;/p&gt;
&lt;h2 id="add-indicators"&gt;Add Indicators&lt;/h2&gt;
&lt;p&gt;The indicators are added through &lt;code&gt;:before&lt;/code&gt; and &lt;code&gt;:after&lt;/code&gt;. They have the same height with the line by setting &lt;code&gt;height&lt;/code&gt; to &lt;code&gt;100%&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To ensure the indicators are aligned to each wrapped line, the picture must be the same height with &lt;code&gt;line-height&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I use following two icons. The height is 28px. So also set &lt;code&gt;span.line&lt;/code&gt;
&lt;code&gt;line-height&lt;/code&gt; to 28px.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Left: &lt;picture class="img-inline-wrapper"&gt;
&lt;img alt="left line wrap indicator" class="img-inline" loading="lazy" src="https://blog.iany.me/2012/02/css-line-wrap-indicator/line-left_hu_7ea5f85ee4a4c98a.png" title="" /&gt;
&lt;/picture&gt;
&lt;/li&gt;
&lt;li&gt;Right: &lt;picture class="img-inline-wrapper"&gt;
&lt;img alt="right line wrap indicator" class="img-inline" loading="lazy" src="https://blog.iany.me/2012/02/css-line-wrap-indicator/line-right_hu_ff318c8c4822bedd.png" title="" /&gt;
&lt;/picture&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;span.line {
line-height: 28px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then just add &lt;code&gt;:before&lt;/code&gt; and &lt;code&gt;:after&lt;/code&gt; and apply vertically repeat background on them. &lt;code&gt;position&lt;/code&gt; of &lt;code&gt;span.line&lt;/code&gt; is set to &lt;code&gt;relative&lt;/code&gt; so &lt;code&gt;:before&lt;/code&gt; and &lt;code&gt;:after&lt;/code&gt; can be positioned relative to it. It is easy to add them in &lt;code&gt;span.line&lt;/code&gt; padding. I use padding instead of margin because later I&amp;rsquo;ll set overflow to hidden to hide indicators outside of &lt;code&gt;span.line&lt;/code&gt;, where margin is considered outside.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;span.line {
padding: 0 13px; /* 8px for indicator, 5px for space around text */
position: relative;
}
span.line:before, span.line:after {
background-repeat: repeat-y;
content: &amp;quot;&amp;quot;;
display: block;
width: 10px;
height: 100%;
position: absolute;
}
span.line:before {
background-image: url(&amp;quot;line-left.png&amp;quot;);
top: 0;
left: 1px;
}
span.line:after {
background-image: url(&amp;quot;line-right.png&amp;quot;)
top: 0;
right: -1px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the indicators are shown in left and right padding on all lines. Use &lt;code&gt;top&lt;/code&gt; to shift down left indicators and &lt;code&gt;bottom&lt;/code&gt; to shift up right indicators, so left indicator is not shown on the first line and right indicator is not shown on the last line. But some indicators are moved out, use &lt;code&gt;overflow:hidden&lt;/code&gt; to hide them.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;span.line {
overflow: hidden;
}
span.line:before {
top: 28px;
}
span.line:after {
top: auto;
bottom: 28px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="add-color-to-padding"&gt;Add Color to Padding&lt;/h2&gt;
&lt;p&gt;Use border and background on &lt;code&gt;pre&lt;/code&gt; to add different color to indicators padding and code block. Use negative margin to align indicators to the
borders.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;pre {
border-style: solid;
border-color: #EEE;
border-width: 0 8px;
background-color: #AAA;
}
span.line {
margin: 0 -8px;
}
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/css/">CSS</category><category domain="https://blog.iany.me/tags/html/">HTML</category></item><item><title>Highlight Ruby New Hash In Emacs</title><link>https://blog.iany.me/2012/01/hilight-ruby-new-hash-in-emacs/</link><pubDate>Thu, 12 Jan 2012 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2012/01/hilight-ruby-new-hash-in-emacs/</guid><description>&lt;p&gt;Not fully tested, let me know if it mess up your buffer.&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://gist.github.com/doitian/1600148"&gt;my-ruby-mode.el&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;(font-lock-add-keywords
'ruby-mode
'((&amp;quot;\\(\\b\\sw[_a-zA-Z0-9]*:\\)\\(?:\\s-\\|$\\)&amp;quot; (1 font-lock-constant-face))))
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/emacs/">Emacs</category><category domain="https://blog.iany.me/tags/ruby/">Ruby</category></item><item><title>Zero Configuration Nginx With Passenger</title><link>https://blog.iany.me/2011/12/zero-configuration-nginx-with-passenger/</link><pubDate>Fri, 09 Dec 2011 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2011/12/zero-configuration-nginx-with-passenger/</guid><description>&lt;p&gt;&lt;a href="http://pow.cx"&gt;Pow&lt;/a&gt; is a zero-config Rack server for Mac OS X. Here I steal its idea to
build a zero-config system based on Nginx with Passenger.&lt;/p&gt;
&lt;h2 id="auto-virtual-hosts"&gt;Auto Virtual Hosts&lt;/h2&gt;
&lt;p&gt;Since 0.8.25, Nginx supports named captures in
&lt;a href="http://wiki.nginx.org/HttpCoreModule#server_name"&gt;server_name&lt;/a&gt; directive.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
server_name ~^(www\.)?(?&amp;lt;domain&amp;gt;.+)$;
root /sites/$domain;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It can be used to setup passenger virtual hosts and locate the rails/rack root
directory based on the server name.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
server_name ~^(.*\.)?(?&amp;lt;app&amp;gt;[^.]+)\.dev$;
root /opt/apps/$app/public;
rails_env development;
passenger_enabled on;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration above matches all sever names that ends with &amp;ldquo;.dev&amp;rdquo;, and use
the 2nd level domain name to locate application root directory. For example,
&lt;code&gt;myapp.dev&lt;/code&gt; and &lt;code&gt;www.myapp.dev&lt;/code&gt; both use the root &lt;code&gt;/opt/apps/myapp/public&lt;/code&gt;,
thus will serve the rails/rack app &lt;code&gt;/opt/apps/myapp/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sure, &lt;code&gt;/opt/apps&lt;/code&gt; can be changed to any directory accessible by Nginx. And
remember to reload Nginx (&lt;code&gt;nginx -s reload&lt;/code&gt;) after change the config file.&lt;/p&gt;
&lt;p&gt;Now new app can be added just like Pow, creating a symbol link in
&lt;code&gt;/opt/apps&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/apps
ln -s /path/to/myapp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can setup more server blocks for different environments, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
server_name ~^(.*\.)?(?&amp;lt;app&amp;gt;[^.]+)\.staging;
root /opt/apps/$app/public;
rails_env staging;
passenger_enabled on;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a record in &lt;code&gt;/etc/hosts&lt;/code&gt; and open browser to see whether &lt;code&gt;http://myapp.dev&lt;/code&gt;
works.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1 myapp.dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="local-dns-resolver"&gt;Local DNS Resolver&lt;/h2&gt;
&lt;p&gt;Pow starts its own DNS and utilizes
&lt;a href="http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man5/resolver.5.html"&gt;Mac /etc/resolver system&lt;/a&gt;
to resolve all subdomains of specified top-level domain to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It is written for nodejs, and I wrote a simple wrapper
&lt;a href="https://github.com/doitian/simpledns"&gt;simpledns&lt;/a&gt; to only start the DNS server
in Pow.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href="http://nodejs.org"&gt;nodejs&lt;/a&gt; first&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone simpledns&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone git://github.com/doitian/simpledns.git
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read README.md in it and start the DNS server.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The server echoes something like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Save following content as files: /etc/resolver/dev
# File generated by simpledns/server.js
namespace: 127.0.0.1
port: 20560
# File content ends here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the instruction and create the file in &lt;code&gt;/etc/resolver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now remove the line &lt;code&gt;127.0.0.1 myapp.dev&lt;/code&gt;, restart your browser to see whether
&lt;code&gt;http://myapp.dev&lt;/code&gt; still works.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/nginx/">Nginx</category><category domain="https://blog.iany.me/tags/rails/">Rails</category><category domain="https://blog.iany.me/tags/ruby/">Ruby</category></item><item><title>[Outdated] Tmux And Rvmrc</title><link>https://blog.iany.me/2011/06/tmux-and-rvmrc/</link><pubDate>Mon, 27 Jun 2011 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2011/06/tmux-and-rvmrc/</guid><description>&lt;blockquote&gt;
&lt;p&gt;RVM has since moved to using .profile, so just put the &amp;ldquo;cd .&amp;rdquo; in .profile and it will work&lt;/p&gt;
&lt;p&gt;&amp;mdash; Mikael Wikman commented below&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Original Article&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://tmux.sourceforge.net/"&gt;Tmux&lt;/a&gt; is a terminal multiplexer. I switched to Tmux from &lt;a href="http://www.gnu.org/software/screen/"&gt;GNU Screen&lt;/a&gt;
recently.&lt;/p&gt;
&lt;p&gt;I work on several Ruby projects. I use &lt;a href="http://beginrescueend.com/"&gt;RVM&lt;/a&gt; to manage gem set for different
projects, and use &lt;a href="https://rvm.io/workflow/rvmrc"&gt;rvmrc&lt;/a&gt; file to switch gem set automatically. I usually start
a Tmux session for a project in its root directory, so all the windows and panes
in the session use the project root as default directory. The problem is, the
new created shell in the session does not load &lt;code&gt;.rvmrc&lt;/code&gt; in the root directory. I
have to force loading the file using &amp;ldquo;&lt;code&gt;cd .&lt;/code&gt;&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I also use &lt;a href="https://github.com/aziz/tmuxinator"&gt;Tmuxinator&lt;/a&gt;. It can specify a rvm gem set though project yml
file. But this setting only has effect on the windows and panes pre-configured
in the yml file, the new created shells do not load &lt;code&gt;.rvmrc&lt;/code&gt;. Moreover, if the
project needs different gem sets for different branches, each gem set must has
its own tmuxinator project file.&lt;/p&gt;
&lt;p&gt;Finally, I figured out a simple solution. Just append &lt;code&gt;cd .&lt;/code&gt; to the end of the
shell init file (&lt;code&gt;.bashrc&lt;/code&gt; for bash and &lt;code&gt;.zshrc&lt;/code&gt; for zsh). Then all the new
shells will try to load the &lt;code&gt;.rvmrc&lt;/code&gt; file in the default directory where it
starts, including shells in Tmux.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# try loading .rvmrc, add it below the line loading RVM
cd .
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/console/">Console</category><category domain="https://blog.iany.me/tags/ruby/">Ruby</category><category domain="https://blog.iany.me/tags/tmux/">Tmux</category></item><item><title>Mendeley: Cross Platform Research Management Tool</title><link>https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/</link><pubDate>Wed, 18 May 2011 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/</guid><description>&lt;p&gt;&lt;a href="http://www.mendeley.com/"&gt;Mendeley&lt;/a&gt; is a research management tool for desktop &amp;amp; web. It has clients for Linux, Mac, Windows and iPhone and a Web interface. Mendeley can manage any document, but is better to work with PDF. The file meta data are synchronized though Mendeley server. The attached files (PDF or any other formats) can also be synchronized, but the free account has a quota of 500MB. However, the attached files directory and underlying sqlite database can be synchronized manually.&lt;/p&gt;
&lt;p&gt;Mendeley is also a collaboration platform that all the added documents can be shared with others. Co-workers can create group to maintain a shared documents list. Mendeley also maintains a public database, from which you can search and add result to your Mendeley library directly. Mendeley also supports importing data from other services such as &lt;a href="http://www.citeulike.org/" title="CiteULike: Everyone&amp;#39;s library"&gt;CiteULike&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mendeley client supports tags and PDF file indexing. It makes easy to search documents. If you use BibTex to manage references in your writings, the client can export all documents into a BibTex file and synchronize your further changes in Mendeley to that file.&lt;/p&gt;
&lt;h2 id="get-started"&gt;Get Started&lt;/h2&gt;
&lt;p&gt;You need to register an account in &lt;a href="http://www.mendeley.com/"&gt;Mendeley&lt;/a&gt;. Then &lt;a href="http://www.mendeley.com/download-mendeley-desktop/"&gt;download&lt;/a&gt; a client for your platform. Following is the snapshot of Mendeley under Linux.&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Mendeley Desktop" class="kg-image" loading="lazy" src="https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/mendeley_hu_92ee6a27072158da.png" srcset="https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/mendeley_hu_eb4398961c0bafe3.png 400w, https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/mendeley_hu_92ee6a27072158da.png 749w" sizes="(max-width: 400px) 100vw, 749px" /&gt;
&lt;figcaption &gt;Mendeley Desktop&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="add-documents"&gt;Add Documents&lt;/h2&gt;
&lt;p&gt;The documents can be added by adding attached files directly. Mendeley will try to extract meta information from files. It is also able to create an entry and edit meta information manually, and attach file later.&lt;/p&gt;
&lt;p&gt;But the most convenient solution is search the documents in &lt;a href="http://www.mendeley.com/research-papers/"&gt;Mendeley database&lt;/a&gt;, add the search result and synchronize to the client, then attach files in client. However, I have not found a way to use a search result as the meta information of an existing document.&lt;/p&gt;
&lt;p&gt;The meta information and attached files can be managed in details panel, which is opened by clicking the handler in the right side.&lt;/p&gt;
&lt;h2 id="synchronize"&gt;Synchronize&lt;/h2&gt;
&lt;p&gt;Documents meta information are all synchronized. The attached files are synchronized according to the per folder settings. All folders are listed under My Library. Just select the folder you want to synchronize and edit its settings to enable.&lt;/p&gt;
&lt;p&gt;If attached files are not synchronize attached files, their local paths are also not synchronized. It is very inconvenient. You cannot synchronize your attached files manually across machines with the same position and let Mendeley just synchronize the meta information.&lt;/p&gt;
&lt;p&gt;However, you can manually synchronize the attached files directory and underlying sqlite database.&lt;/p&gt;
&lt;h3 id="organize-files"&gt;Organize files&lt;/h3&gt;
&lt;p&gt;Open options dialog through menu &lt;em&gt;Tools -&amp;gt; Options&lt;/em&gt; and switch to &amp;ldquo;File Organizer&amp;rdquo; tab.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enable &amp;ldquo;Organize my files&amp;rdquo; and select a directory such as &amp;ldquo;/Mendeley&amp;rdquo;. It must be the same in different machines. Otherwise the manual solution cannot synchronize between Windows and Mac, Linux. Since in Windows, the path name contains the driver name, so the database also cannot be shared between Windows and other systems having root path.&lt;/li&gt;
&lt;li&gt;Enable &amp;ldquo;Sort files into sub-folders&amp;rdquo; to avoid file name conflicts. I prefer the Folder path &lt;code&gt;Year&lt;/code&gt;/&lt;code&gt;Author&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="File Organizer" class="kg-image" loading="lazy" src="https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/file_organizer_hu_c30e7da4e47f207c.png" srcset="https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/file_organizer_hu_2bb874bf1e2aeade.png 400w, https://blog.iany.me/2011/05/mendeley-cross-platform-research-management-tool/file_organizer_hu_c30e7da4e47f207c.png 519w" sizes="(max-width: 400px) 100vw, 519px" /&gt;
&lt;figcaption &gt;File Organizer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="manual-synchronization"&gt;Manual Synchronization&lt;/h3&gt;
&lt;p&gt;The attached files directory and the meta info sqlite database can be synchronized using &lt;a href="http://db.tt/I4zEuqN" title="cloud service for file synchronization"&gt;Dropbox&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Rsync" title="file synchronization command"&gt;Rsync&lt;/a&gt; or &lt;a href="http://code.google.com/p/lsyncd/" title="Live Syncing (Mirror) Daemon"&gt;Lsyncd&lt;/a&gt;. For Dropbox, just make symbol links in the Dropbox directory.&lt;/p&gt;
&lt;p&gt;The database file is created when you have logged-in in the client. It is stored in different location in different platform. I have figured out its location in Linux and Mac (replace &lt;a href="mailto:email@example.com"&gt;email@example.com&lt;/a&gt; with your own registered email).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux: &lt;code&gt;$HOME/.local/share/data/Mendeley Ltd./Mendeley Desktop/email@example.com@www.mendeley.com.sqlite&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mac: &lt;code&gt;$HOME/Library/Application Support/Mendeley Desktop/email@example.com@www.mendeley.com.sqlite&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because the sqlite database file is binary, you cannot simply merge two database files. I recommend to add and edit documents only in one machine, and use other machines as read only copies. Otherwise you may loose data during synchronization.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category><category domain="https://blog.iany.me/tags/reference-management/">Reference Management</category><category domain="https://blog.iany.me/tags/software/">Software</category></item><item><title>Switch Window Using Fuzz Matching</title><link>https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/</link><pubDate>Wed, 25 Aug 2010 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/</guid><description>&lt;p&gt;This article demonstrates how to quickly switch to a window using &lt;strong&gt;gpicker&lt;/strong&gt;
and &lt;strong&gt;wmctrl&lt;/strong&gt;. You type significant letters of workspace name, application name
or title and gpicker provides a list of windows you most likely mean to pick.&lt;/p&gt;
&lt;h2 id="gpicker"&gt;Gpicker&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://savannah.nongnu.org/projects/gpicker" title="Gpicker"&gt;Gpicker&lt;/a&gt; is a program that allows you to quickly and conveniently pick file
in a (possibly very large) project. You type significant letters of file name
(typically from the start of words) and gpicker provides you with a list of
files you most likely mean to pick. The program filters and orders project&amp;rsquo;s
list of files in real-time as you type.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You need to &lt;a href="http://ftp.twaren.net/Unix/NonGNU/gpicker/"&gt;download&lt;/a&gt; and install
it manually (for Ubuntu, you can download the Debian package).&lt;/p&gt;
&lt;p&gt;Gpicker is designed as a filter, so it can be used to select item from an
arbitrary list. The selected item is printed on standard output.&lt;/p&gt;
&lt;p&gt;For example, use gpicker to select a number between 1 and 10:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ seq -w 1 10 | gpicker -n &amp;quot;\n&amp;quot; -
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="kg-image-card kg-width-fit"&gt;
&lt;img alt="Gpicker picks 1 to 10" class="kg-image" loading="lazy" src="https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/picker_1_to_10_hu_6a8e747f65bb80c3.png" /&gt;
&lt;figcaption &gt;Gpicker picks 1 to 10&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The last dash tells gpicker to read list from standard input, and option &amp;ldquo;-n&amp;rdquo;
sets the list separator. Gpicker uses &amp;ldquo;\0&amp;rdquo; as item separator by default.&lt;/p&gt;
&lt;h2 id="wmctrl"&gt;Wmctrl&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Wmctrl&lt;/strong&gt; is a command line utility to interact with a EWMH/NetWM compatible X
Window Manager. It can list all windows, switch to a window or bring a window to
current workspace. Wmctrl can be installed by package manager in most Linux
distribution.&lt;/p&gt;
&lt;p&gt;Wmctrl lists all windows using option &amp;ldquo;-l&amp;rdquo;. We can add &amp;ldquo;-x&amp;rdquo; to list windows
resource and class as well. The output of &lt;code&gt;wmctrl -l -x&lt;/code&gt; looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x03200009 0 urxvt.URxvt ian-desktop urxvt
0x0340008e 1 Navigator.Firefox ian-desktop Vimperator / test
0x03e000a4 2 emacs.Emacs ian-desktop Emacs: _theme.sass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first column is the window ID. We can switch to or bring a window using the
ID.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# switch to Emacs, raise and focus it
wmctrl -i -a 0x03e000a4
# bring Emacs to current workspace, raise and focus it
wmctrl -i -R 0x03e000a4
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="windows-picker"&gt;Windows Picker&lt;/h2&gt;
&lt;p&gt;Now we can use wmctrl to list windows, gpicker to select one window, and
wmctrl again to switch to the selected window.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wmctrl -i -a `wmctrl -l -x | gpicker -n &amp;quot;\n&amp;quot; -d &amp;quot;\n&amp;quot; -`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The directory separator is set to &amp;ldquo;\n&amp;rdquo; in the first windows picker above,
because gpicker is designed to pick files primarily and it has special rules
in the matching algorithm.&lt;/p&gt;
&lt;p&gt;First the item is split by directory separator. Before we enter a directory
separator (&amp;quot;/&amp;quot; by default), only the last component (it is the file name if the
item is a file path) is used in matching. For example, &amp;ldquo;bin&amp;rdquo; does not match item
&amp;ldquo;bin/ruby&amp;rdquo;, but &amp;ldquo;bin/&amp;rdquo;, even &amp;ldquo;b/&amp;rdquo; does match. If the window title contains the
directory separator, for example the Firefox in the &lt;code&gt;wmctrl -l -x&lt;/code&gt; output above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x0340008e 1 Navigator.Firefox ian-desktop Vimperator / test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To match it, we must enter &amp;ldquo;firefox/&amp;rdquo;. So I set directory separator to &amp;ldquo;\n&amp;rdquo; to
disable this feature, and the whole item is used to match.&lt;/p&gt;
&lt;p&gt;However, we can take advantage of this feature. If we construct the window item
likes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id/workspace/window_class/window_title
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can match window title by type significant letters directory. I also can
use directory separator &amp;ldquo;/&amp;rdquo; to filter all windows in a specific workspace, or
windows with the same window class.&lt;/p&gt;
&lt;p&gt;It is very useful for me. Because I uses tiling windows manager
&lt;a href="http://xmonad.org/"&gt;Xmonad&lt;/a&gt; and places windows in different windows. Following
are some examples:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;I have entered&lt;/th&gt;
&lt;th&gt;Windows I want to match&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;www/ff/&lt;/td&gt;
&lt;td&gt;Firefox in workspace &amp;ldquo;2.www&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ruby&lt;/td&gt;
&lt;td&gt;Windows which title contains ruby&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xpdf/ruby&lt;/td&gt;
&lt;td&gt;A document opened using xpdf and the title contains ruby&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sys/usrshare&lt;/td&gt;
&lt;td&gt;The terminal I opened visit /usr/share in workspace &amp;ldquo;1.sys&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figure class="kg-card kg-gallery-card kg-width-wide"&gt;
&lt;div class="kg-gallery-container"&gt;
&lt;div class="kg-gallery-row"&gt;
&lt;div class="kg-gallery-image"&gt;&lt;img alt="Windows list" class="tippy" loading="lazy" width="794" height="279" src="https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/picker-windows-list_hu_46800c8c68c0f197.png" srcset="https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/picker-windows-list_hu_6c6ac2dfaf6a34b8.png 400w, https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/picker-windows-list_hu_46800c8c68c0f197.png 794w" sizes="(max-width: 400px) 100vw, 794px" data-tippy-content="Windows list" /&gt;&lt;/div&gt;
&lt;div class="kg-gallery-image"&gt;&lt;img alt="Select Ruby document opened in Xpdf" class="tippy" loading="lazy" width="794" height="279" src="https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/picker-xpdf-ruby_hu_9373da17500fd47a.png" srcset="https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/picker-xpdf-ruby_hu_2a938b86526b9b51.png 400w, https://blog.iany.me/2010/08/switch-window-using-fuzz-matching/picker-xpdf-ruby_hu_9373da17500fd47a.png 794w" sizes="(max-width: 400px) 100vw, 794px" data-tippy-content="Select Ruby document opened in Xpdf" /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Sure, we have to remove &amp;ldquo;/&amp;rdquo; from workspace name and window title.&lt;/p&gt;
&lt;p&gt;I use a Ruby &lt;a href="https://gist.github.com/doitian/551432"&gt;script&lt;/a&gt; to format the
list. Workspace name can be listed using &lt;code&gt;wmctrl -d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now we can use the ruby script in the revised version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wmctrl -i -a `wlist.rb | gpicker - | sed 's;/.*$;;'`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &amp;ldquo;-a&amp;rdquo; with &amp;ldquo;-R&amp;rdquo; to bring window to current workspace.&lt;/p&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/desktop/">Desktop</category><category domain="https://blog.iany.me/tags/linux/">Linux</category><category domain="https://blog.iany.me/tags/productivity/">Productivity</category><category domain="https://blog.iany.me/tags/script/">Script</category></item><item><title>Study on Alias Method</title><link>https://blog.iany.me/2010/05/study-on-alias-method/</link><pubDate>Sat, 29 May 2010 00:00:00 +0000</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/2010/05/study-on-alias-method/</guid><description>&lt;p&gt;&lt;a href="http://twitter.com/miloyip"&gt;@miloyip&lt;/a&gt; has published a &lt;a href="http://www.cnblogs.com/miloyip/archive/2010/05/27/reply_discrete.html"&gt;post&lt;/a&gt; recently which motioned the Alias Method to generate a discrete random variable in &lt;em&gt;O(1)&lt;/em&gt;. After some research, I find out that it is a neat and clever algorithm. Following are some notes of my study on it.&lt;/p&gt;
&lt;h2 id="what-is-alias-method"&gt;What is Alias Method&lt;/h2&gt;
&lt;p&gt;Alias method is an efficient algorithm to generate a discrete random variable with specified probability mass function using a uniformly distributed random variable.&lt;/p&gt;
&lt;p&gt;Let &lt;code&gt;$Z$&lt;/code&gt; be the discrete random variable which has n possible outcomes &lt;code&gt;$z_0,z_1,\ldots,z_{n-1}$&lt;/code&gt;. To make the discussion below simple, we study another variable &lt;code&gt;$Y$&lt;/code&gt;, where &lt;code&gt;$P\{Y=i\}=P\{Z=z_i\}$&lt;/code&gt;. And when &lt;code&gt;$Y$&lt;/code&gt; takes on value &lt;code&gt;$i$&lt;/code&gt;, let &lt;code&gt;$Z$&lt;/code&gt; be &lt;code&gt;$z_i$&lt;/code&gt;. So &lt;code&gt;$Z$&lt;/code&gt; can be generated from &lt;code&gt;$Y$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Random variable &lt;code&gt;$X$&lt;/code&gt; is uniformly distributed in &lt;code&gt;$(0, n)$&lt;/code&gt;, which probability density function is&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
f(x) = \left\{
\begin{array}{rl}
1/n &amp;amp; \text{if } 0 &amp;lt; x &amp;lt; n\\
0 &amp;amp; \text{otherwise}\\
\end{array} \right.
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now generate a variable &lt;code&gt;$Y'$&lt;/code&gt; that&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
Y' = \left\{
\begin{array}{rl}
\lfloor x \rfloor &amp;amp; \text{if } (x - \lfloor x \rfloor) &amp;lt; F(\lfloor x \rfloor)\\
A(\lfloor x \rfloor) &amp;amp; \text{otherwise}\\
\end{array} \right.
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$A(i)$&lt;/code&gt; is the alias function. When &lt;code&gt;$x$&lt;/code&gt; falls in range &lt;code&gt;$[i, i + 1)$&lt;/code&gt; (&lt;code&gt;$i$&lt;/code&gt; is an integer), &lt;code&gt;$y$&lt;/code&gt; has the probability &lt;code&gt;$F(i)$&lt;/code&gt; to be &lt;code&gt;$i$&lt;/code&gt;, and probability &lt;code&gt;$1 - F(i)$&lt;/code&gt; to be &lt;code&gt;$A(i)$&lt;/code&gt;. Because &lt;code&gt;$x$&lt;/code&gt; is uniformly distributed,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\begin{aligned}
P\{x \in [i, i + F(i))\} &amp;amp;= \displaystyle\int_i^{i+F(i)}\frac{1}{n}dx\\
&amp;amp;= (i + F(i) - i) \times 1/n\\
&amp;amp;= F(i)/n,\\
\\
P\{x \in [i + F(i), i + 1)\} &amp;amp;= \displaystyle\int_{i+F(i)}^{i+1}\frac{1}{n}dx\\
&amp;amp;= (i + 1 - (i + F(i))) \times 1/n\\
&amp;amp;= (1-F(i))/n
\end{aligned}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;rsquo;s denote the set of values &lt;code&gt;$j$&lt;/code&gt; that satisfies &lt;code&gt;$A(j) = i$&lt;/code&gt; as &lt;code&gt;$A^{-1}(i)$&lt;/code&gt;. The generated variable &lt;code&gt;$Y'$&lt;/code&gt; has following probability mass function:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
P\{Y' = i\} = F(i)/n + \sum_{j \in A^{-1}(i)}\frac{1-F(j)}{n}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alias method is the algorithm to construct &lt;code&gt;$A$&lt;/code&gt; and &lt;code&gt;$F$&lt;/code&gt; so that &lt;code&gt;$P\{Y' = i\}$&lt;/code&gt; equals to &lt;code&gt;$P\{Y = i\}$&lt;/code&gt; for all &lt;code&gt;$i$&lt;/code&gt;. Because the domain of both &lt;code&gt;$A$&lt;/code&gt; and &lt;code&gt;$F$&lt;/code&gt; are integers &lt;code&gt;$0,1,\ldots,n-1$&lt;/code&gt;, they can be stored in array and values can be looked up in &lt;em&gt;O(1)&lt;/em&gt;, where the space efficiency is in &lt;em&gt;O(n)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In miloyip&amp;rsquo;s implementation, &lt;code&gt;$A$&lt;/code&gt; and &lt;code&gt;$F$&lt;/code&gt; are stored in &lt;code&gt;std::vector&amp;lt;AliasItem&amp;gt; mAliasTable&lt;/code&gt;, where &lt;code&gt;$A$&lt;/code&gt;&amp;rsquo;s values are stored in &lt;code&gt;AliasItem::index&lt;/code&gt; and &lt;code&gt;$F$&lt;/code&gt;&amp;rsquo;s values are &lt;code&gt;AliasItem::prob&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="algorithm"&gt;Algorithm&lt;/h2&gt;
&lt;h3 id="construct-steps"&gt;Construct Steps&lt;/h3&gt;
&lt;p&gt;Initialize the set &lt;code&gt;$S$&lt;/code&gt; to be &lt;code&gt;${0,1,\ldots,n-1}$&lt;/code&gt; and n variables &lt;code&gt;$p_i$&lt;/code&gt; that with values:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ p_i = P\{Y=i\}, i \in S \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Denote the number of elements in &lt;code&gt;$S$&lt;/code&gt; as &lt;code&gt;$\|S\|$&lt;/code&gt;. We have a important invariant that&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ \sum_{i \in S}{p_i} = \|S\| / n \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the beginning of the algorithm, the invariant holds because the sum of all probabilities must equal to 1.&lt;/p&gt;
&lt;p&gt;The algorithm is performed using following steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there is an element &lt;code&gt;$i$&lt;/code&gt; in set &lt;code&gt;$S$&lt;/code&gt; such that &lt;code&gt;$p_i &amp;lt; 1/n$&lt;/code&gt;, there must be a &lt;code&gt;$j$&lt;/code&gt; in set &lt;code&gt;$S$&lt;/code&gt; such that &lt;code&gt;$p_j &amp;gt; 1/n$&lt;/code&gt;.&lt;sup id="fnref:1"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;1&lt;/span&gt;&lt;/sup&gt; Let &lt;code&gt;$A(i) = j$&lt;/code&gt; and &lt;code&gt;$F(i) = p_i / (1/n) = p_i \times n$&lt;/code&gt;. Remove &lt;code&gt;$i$&lt;/code&gt; from &lt;code&gt;$S$&lt;/code&gt; and subtract &lt;code&gt;$n/1 - p_i$&lt;/code&gt; from &lt;code&gt;$p_j$&lt;/code&gt;. It is easy to verify that the invariant still holds after these changes.&lt;sup id="fnref:2"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;2&lt;/span&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Repeat step 1 until &lt;code&gt;$S$&lt;/code&gt; is empty or there is no more elements &lt;code&gt;$i$&lt;/code&gt; in &lt;code&gt;$S$&lt;/code&gt; that &lt;code&gt;$p_i &amp;lt; 1/n$&lt;/code&gt;. If &lt;code&gt;$S$&lt;/code&gt; is empty, the algorithm finishes. Otherwise for all remaining &lt;code&gt;$i$&lt;/code&gt; in &lt;code&gt;$S$&lt;/code&gt;, we must have &lt;code&gt;$p_i = 1/n$&lt;/code&gt;.&lt;sup id="fnref:3"&gt;&lt;span class="footnote-ref" role="doc-noteref"&gt;3&lt;/span&gt;&lt;/sup&gt; Let &lt;code&gt;$A(i)=i$&lt;/code&gt; and &lt;code&gt;$F(i)=p_i\times n=1$&lt;/code&gt; for all remaining &lt;code&gt;$i$&lt;/code&gt;, and remove them from the set &lt;code&gt;$S$&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The algorithm finishes when &lt;code&gt;$S$&lt;/code&gt; becomes empty, and an element is removed only when its corresponding &lt;code&gt;$A$&lt;/code&gt; and &lt;code&gt;$F$&lt;/code&gt; has been determined, so all values of &lt;code&gt;$A$&lt;/code&gt; and &lt;code&gt;$F$&lt;/code&gt; has been generated.&lt;/p&gt;
&lt;p&gt;In miloyip&amp;rsquo;s implementation, &lt;code&gt;$p_i$&lt;/code&gt; is stored in &lt;code&gt;AliasItem::prob&lt;/code&gt; before &lt;code&gt;$i$&lt;/code&gt; is removed from the set. When &lt;code&gt;$i$&lt;/code&gt; is removed from the set, &lt;code&gt;AliasItem::prob&lt;/code&gt; is set to &lt;code&gt;$F(i)$&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="correctness"&gt;Correctness&lt;/h3&gt;
&lt;p&gt;The invariant holds at the beginning and at the end of each step, it guarantees that the algorithm can finish. It is easy to prove it using mathematical induction. So we only need to prove &lt;code&gt;$P\{Y'=i\}=P\{Y=i\}$&lt;/code&gt; for any &lt;code&gt;$i$&lt;/code&gt;, i.e.,&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ P\{Y = i\} = F(i)/n + \sum_{j \in A^{-1}(i)}\frac{1-F(j)}{n} \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Denote &lt;code&gt;$p'_i$&lt;/code&gt; as the value of &lt;code&gt;$p_i$&lt;/code&gt; when &lt;code&gt;$i$&lt;/code&gt; is removed from set &lt;code&gt;$S$&lt;/code&gt;. Check the construction steps again, we get following properties:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No &lt;code&gt;$p_i$&lt;/code&gt; can increase. Thus &lt;code&gt;$p_i &amp;lt;= P\{Y=i\}$&lt;/code&gt; in all steps and &lt;code&gt;$p'_i &amp;lt;= P\{X=i\}$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$p_i$&lt;/code&gt; decreases only when its initial value &lt;code&gt;$P\{Y=i\}&amp;gt;1/n$&lt;/code&gt;. So if &lt;code&gt;$P\{Y=i\}&amp;lt;=1/n$&lt;/code&gt;, &lt;code&gt;$p_i = P\{Y=i\}$&lt;/code&gt; throughout the algorithm and &lt;code&gt;$p'_i=P\{Y=i\}$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$F(i) = p'_i \times n$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$i$&lt;/code&gt; is removed only when &lt;code&gt;$p_i \leq 1/n$&lt;/code&gt;, i.e., &lt;code&gt;$p'_i \leq 1/n$&lt;/code&gt;, thus &lt;code&gt;$F(i)=p'_i \times n \leq 1$&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$A(j)$&lt;/code&gt; is set to a value &lt;code&gt;$i \neq j$&lt;/code&gt; only if &lt;code&gt;$p_i &amp;gt; 1/n$&lt;/code&gt; (see step 1), i.e., &lt;code&gt;$P\{Y=i\}&amp;gt;1/n$&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now consider value &lt;code&gt;$i$&lt;/code&gt; when &lt;code&gt;$P\{Y=i\}&amp;lt;1/n$&lt;/code&gt;, &lt;code&gt;$P\{Y=i\}=1/n$&lt;/code&gt; and &lt;code&gt;$P\{Y=i\}&amp;gt;1/n$&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="pyi--1n"&gt;P{Y=i} &amp;lt; 1/n&lt;/h4&gt;
&lt;p&gt;If &lt;code&gt;$P\{Y=i\} &amp;lt; 1/n$&lt;/code&gt;, from property 2 and property 3, &lt;code&gt;$F(i) = p'_i \times n = P\{Y=i\} \times n$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Apparently &lt;code&gt;$A^{-1}(i) = {}$&lt;/code&gt;, because &lt;code&gt;$A$&lt;/code&gt; is either set to value &lt;code&gt;$j$&lt;/code&gt; where &lt;code&gt;$p_j&amp;gt;1/n$&lt;/code&gt; in step 1 or &lt;code&gt;$k$&lt;/code&gt; where &lt;code&gt;$p_k = 1/n$&lt;/code&gt; in step 2.&lt;/p&gt;
&lt;p&gt;Thus&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[
\begin{aligned}
&amp;amp;F(i)/n + \sum_{j \in A^{-1}(i)}\frac{1-F(j)}{n}\\
=&amp;amp;F(i)/n\\
=&amp;amp;P\{Y=i\} \times n / n\\
=&amp;amp;P\{Y=i\}
\end{aligned}
\]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which completes the proof.&lt;/p&gt;
&lt;h4 id="pyi--1n-1"&gt;P{Y=i} = 1/n&lt;/h4&gt;
&lt;p&gt;If &lt;code&gt;$P\{Y=i\} = 1/n$&lt;/code&gt;, apparently &lt;code&gt;$A(i) = i$&lt;/code&gt;. If there&amp;rsquo;s another value &lt;code&gt;$j\neq~i$&lt;/code&gt; also satisfies &lt;code&gt;$A(j) = i$&lt;/code&gt;, from property 4, &lt;code&gt;$P\{Y=i\} &amp;gt; 1/n$&lt;/code&gt;, conflict with the condition. So &lt;code&gt;$A^{-1}(i) = {i}$&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Thus&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ \begin{aligned}
&amp;amp;F(i)/n + \sum_{j \in A^{-1}(i)}\frac{1-F(j)}{n}\\
=&amp;amp;F(i)/n + (1-F(i))/n\\
=&amp;amp;1/n
\end{aligned} \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which completes the proof.&lt;/p&gt;
&lt;h4 id="pyi--1n-2"&gt;P{Y=i} &amp;gt; 1/n&lt;/h4&gt;
&lt;p&gt;When &lt;code&gt;$P\{Y=i\} &amp;gt; 1/n$&lt;/code&gt;, apparently i is not in &lt;code&gt;$A^{-1}(i)$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Consider each value &lt;code&gt;$j$&lt;/code&gt; in set &lt;code&gt;$A^{-1}(i)$&lt;/code&gt;. Once &lt;code&gt;$j$&lt;/code&gt; is removed from &lt;code&gt;$S$&lt;/code&gt;, &lt;code&gt;$A(j)$&lt;/code&gt; is set to &lt;code&gt;$i$&lt;/code&gt; and &lt;code&gt;$1/n - p'_j$&lt;/code&gt; is subtracted from &lt;code&gt;$p_i$&lt;/code&gt;. Thus&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ p'_i = P\{Y=i\} - \sum_{j \in A^{-1}(i)}(1/n - p'_j) \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-katex"&gt;\[ \begin{aligned}
&amp;amp;F(i)/n + \sum_{j \in A^{-1}(i)}\frac{1-F(j)}{n}\\
=&amp;amp;p'_i \times n / n + \sum_{j \in A^{-1}(i)}\frac{1-(p'_j \times~n)}{n}\\
=&amp;amp;P\{Y=i\} - \sum_{j \in A^{-1}(i)}(1/n - p'_j)\ + \sum_{j \in A^{-1}(i)}(1/n - p'_j)\\
=&amp;amp;P\{Y=i\}
\end{aligned} \]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For all &lt;code&gt;$i$&lt;/code&gt;, &lt;code&gt;$P\{Y'=i\} = P\{Y=i\}$&lt;/code&gt;, the proof completes.&lt;/p&gt;
&lt;h2 id="intuitive-presentation"&gt;Intuitive Presentation&lt;/h2&gt;
&lt;p&gt;The algorithm can be presented in intuitive meaning. The range &lt;code&gt;$(0, n]$&lt;/code&gt; is split into n consecutive sub ranges &lt;code&gt;$(i, i + 1]$&lt;/code&gt; for &lt;code&gt;$i = 0, 1, \ldots, n - 1$&lt;/code&gt;. The probability of &lt;code&gt;$X$&lt;/code&gt; falls into any range is &lt;code&gt;$(i + 1 - i) \times 1/n = 1/n$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;$P\{Y=i\} = 1/n$&lt;/code&gt;, we can allocate the whole slot &lt;code&gt;$i$&lt;/code&gt; to it. Let &lt;code&gt;$Y=i$&lt;/code&gt; when &lt;code&gt;$x$&lt;/code&gt; falls in &lt;code&gt;$(i, i + 1]$&lt;/code&gt; which has the probability &lt;code&gt;$1/n$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;$P\{Y=i\} &amp;lt; 1/n$&lt;/code&gt;, we can allocate the starting part &lt;code&gt;$(i,i+n\times~P\{Y=i\}]$&lt;/code&gt; in &lt;code&gt;$(i,i+1]$&lt;/code&gt;. Let &lt;code&gt;$Y = i$&lt;/code&gt; when &lt;code&gt;$x$&lt;/code&gt; falls in &lt;code&gt;$(i, i + n\times P\{Y=i\}]$&lt;/code&gt;, where the probability is &lt;code&gt;$n\times~P\{Y=i\}\times(1/n)=P\{Y=i\}$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;$P\{Y=i\} &amp;gt; 1/n$&lt;/code&gt;, we can allocate unused ranges in &lt;code&gt;$(j + n\times P\{Y=j\}, j + 1]$&lt;/code&gt; for any &lt;code&gt;$j$&lt;/code&gt; that &lt;code&gt;$P\{Y=j\} &amp;lt; 1/n$&lt;/code&gt;. However, unused range is not allowed to be split again.&lt;/p&gt;
&lt;p&gt;See the figure below, which demonstrates how to generate &lt;code&gt;$Y$&lt;/code&gt; with probability mass function &lt;code&gt;$n = 5$&lt;/code&gt;&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Alias Method" class="kg-image" loading="lazy" src="https://blog.iany.me/2010/05/study-on-alias-method/alias-method_hu_31ea1afc8864766a.png" srcset="https://blog.iany.me/2010/05/study-on-alias-method/alias-method_hu_e264010de3a2ddd9.png 400w, https://blog.iany.me/2010/05/study-on-alias-method/alias-method_hu_31ea1afc8864766a.png 600w" sizes="(max-width: 400px) 100vw, 600px" /&gt;
&lt;figcaption &gt;Alias Method&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$P\{Y=0\} = 0.16$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$P\{Y=1\} = 0.1$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$P\{Y=2\} = 0.32$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$P\{Y=3\} = 0.22$&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$P\{Y=4\} = 0.2$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;$P\{Y=4\}=1/n$&lt;/code&gt;, so let &lt;code&gt;$Y = 4$&lt;/code&gt; only when &lt;code&gt;$x$&lt;/code&gt; falls in &lt;code&gt;$(4, 5]$&lt;/code&gt;, which probability is &lt;code&gt;$(5-4)\times 0.2 = 0.2$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$P\{Y=0\}=0.16&amp;lt;0.2$&lt;/code&gt;, so let &lt;code&gt;$Y = 0$&lt;/code&gt; only when &lt;code&gt;$x$&lt;/code&gt; falls in &lt;code&gt;$(0,0.16\times~5]$&lt;/code&gt;, i.e., &lt;code&gt;$(0,0.8]$&lt;/code&gt;, which probability is &lt;code&gt;$(0.8-0)\times~0.2=0.16$&lt;/code&gt;. &lt;code&gt;$(0.8,1]$&lt;/code&gt; is unused.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$P\{Y=1\}$&lt;/code&gt; is the same. &lt;code&gt;$(1,1.5]$&lt;/code&gt; is allocated and &lt;code&gt;$(1.5,2]$&lt;/code&gt; is unused.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$P\{Y=2\} = 0.32 &amp;gt; 0.2$&lt;/code&gt;, it needs ranges with total length &lt;code&gt;$0.32\times~5=1.6$&lt;/code&gt;. We allocate the range &lt;code&gt;$(0.8, 1]$&lt;/code&gt; and &lt;code&gt;$(1.5, 2]$&lt;/code&gt;. The remaining length &lt;code&gt;$1.6-0.2-0.5=0.9&amp;lt;1$&lt;/code&gt;, then we can allocate a part of its own slot. Finally, three ranges have been allocated, &lt;code&gt;$(0.8,1]$&lt;/code&gt;, &lt;code&gt;$(1.5,2]$&lt;/code&gt; and &lt;code&gt;$(2,2.9]$&lt;/code&gt;. &lt;code&gt;$(2.9,3]$&lt;/code&gt; is unused.&lt;/p&gt;
&lt;p&gt;Follow the same step to handle &lt;code&gt;$Y=3$&lt;/code&gt;. The final allocation is depicted in &lt;code&gt;$D$&lt;/code&gt;. The allocation is not unique, &lt;code&gt;$F$&lt;/code&gt; depicts another solution.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://portal.acm.org/citation.cfm?id=355749"&gt;An Efficient Method for Generating Discrete Random Variables with General Distributions&lt;/a&gt;,
Alastair J. Walker&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.cnblogs.com/miloyip/archive/2010/05/27/reply_discrete.html"&gt;回应CSDN肖舸《做程序，要“专注”和“客观”》，实验比较各离散采样算法 - Milo的游戏开发 - 博客园&lt;/a&gt;,
Milo Yip&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;If all &lt;code&gt;$j$&lt;/code&gt; except &lt;code&gt;$i$&lt;/code&gt; that &lt;code&gt;$p_j \leq 1/n$&lt;/code&gt;, Sum up both end of the
inequalities for all &lt;code&gt;$j$&lt;/code&gt; and &lt;code&gt;$p_i &amp;lt; 1/n$&lt;/code&gt;, we can get
&lt;code&gt;$\sum_{i \in S}{p_i} &amp;lt; \|S\| / n$&lt;/code&gt; which is conflict with the invariant.&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;The right side has decreased &lt;code&gt;$1/n$&lt;/code&gt; because &lt;code&gt;$\|S\|$&lt;/code&gt; has decreased 1. The
left side has decreased &lt;code&gt;$p_i + (n/1 - p_i) = 1/n$&lt;/code&gt;, because &lt;code&gt;$i$&lt;/code&gt; is removed from the
set and &lt;code&gt;$(n/1 - p_i)$&lt;/code&gt; is subtracted from &lt;code&gt;$p_j$&lt;/code&gt;. Thus both side decrease the same
amount, the equality still holds.&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Because no &lt;code&gt;$p_i &amp;lt; 1/n$&lt;/code&gt;, then &lt;code&gt;$p_i \geq 1/n$&lt;/code&gt;. To satisfy the invariant, no &lt;code&gt;$p_i$&lt;/code&gt;
can be larger then &lt;code&gt;$1/n$&lt;/code&gt;. Thus for all &lt;code&gt;$i$&lt;/code&gt; in &lt;code&gt;$S$&lt;/code&gt;, &lt;code&gt;$p_i = 1/n$&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category domain="https://blog.iany.me/post/">Posts</category><category domain="https://blog.iany.me/tags/algorithm/">Algorithm</category><category domain="https://blog.iany.me/tags/math/">Math</category><category domain="https://blog.iany.me/tags/probability/">Probability</category></item></channel></rss>