1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
|
<feed xmlns="http://www.w3.org/2005/Atom"><title>dl? cmplnts?</title><subtitle>Zhiming Wang's personal blog</subtitle><link href="http://archive.zhimingwang.org/atom.xml" rel="self" type="application/atom+xml"/><link href="http://archive.zhimingwang.org/" rel="alternate" type="text/html"/><updated>2017-04-28T00:29:42+02:00</updated><id>http://archive.zhimingwang.org/</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><icon>http://archive.zhimingwang.org/img/icon-400.png</icon><generator uri="https://github.com/zmwangx/zmwangx.github.io">pyblog</generator><entry><title type="html">pyenv: compiling Python with SQLite in nonstandard location</title><link href="http://archive.zhimingwang.org/blog/2016-10-26-pyenv-compiling-python-with-sqlite-in-nonstandard-location.html"/><updated>2016-10-26T12:16:22-04:00</updated><id>http://archive.zhimingwang.org/blog/2016-10-26-pyenv-compiling-python-with-sqlite-in-nonstandard-location.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>This is a quick post sharing a workaround that I needed just now.</p>
<p>I was trying to compile Pythons with pyenv on a RHEL 6.8 cluster. Unfortunately <code>sqlite-devel</code> is not installed and I doubt I can convince my sysadmin to install a package for me. The lack of SQLite headers resulted in Pythons without <code>_sqlite3</code> which is essential for me. Hinting at SQLite headers from Linuxbrew with <code>CPATH</code> did not help either.</p>
<p>Digging into CPython source code, turns out that CPython only looks into <a href="https://github.com/python/cpython/blob/59fa72e34da71fb24f52251c1cc88ed3c3b14797/setup.py#L1132-L1138">a fixed set of paths</a>:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python">sqlite_inc_paths <span class="op">=</span> [ <span class="st">'/usr/include'</span>,
<span class="st">'/usr/include/sqlite'</span>,
<span class="st">'/usr/include/sqlite3'</span>,
<span class="st">'/usr/local/include'</span>,
<span class="st">'/usr/local/include/sqlite'</span>,
<span class="st">'/usr/local/include/sqlite3'</span>,
]
<span class="cf">if</span> cross_compiling:
sqlite_inc_paths <span class="op">=</span> []</code></pre></div>
<p>Well that's unfortunate. Luckily pyenv makes it really easy to patch Python source code; take a look at <a href="https://github.com/yyuu/pyenv/tree/master/plugins/python-build/share/python-build/patches"><code>plugins/python-build/share/python-build/patches</code></a> and you'll get the idea. Therefore, in the case of Linuxbrew'ed pyenv and SQLite, say we want to build Python 3.5.2 with SQLite support, we simply put the following patch at <code>~/.linuxbrew/opt/pyenv/plugins/python-build/share/python-build/patches/3.5.2/Python-3.5.2/linuxbrew-sqlite3.patch</code>:</p>
<div class="sourceCode"><pre class="sourceCode diff"><code class="sourceCode diff"><span class="kw">diff --git a/setup.py b/setup.py</span>
index 174ce72..774fd65 100644
<span class="dt">--- a/setup.py</span>
+++ b/setup.py
@@ -1108,6 +1108,7 @@ class PyBuildExt(build_ext):
'/usr/local/include',
'/usr/local/include/sqlite',
'/usr/local/include/sqlite3',
+ os.path.expanduser('~/.linuxbrew/opt/sqlite/include/'),
]
if cross_compiling:
sqlite_inc_paths = []</code></pre></div>
<p>That's it. Now</p>
<pre><code>$ pyenv install 3.5.2</code></pre>
<p>and enjoy.</p>
]]></content></entry><entry><title type="html">This blog is now behind CloudFlare</title><link href="http://archive.zhimingwang.org/blog/2016-09-01-this-blog-is-now-behind-cloudflare.html"/><updated>2016-09-01T20:11:00+08:00</updated><id>http://archive.zhimingwang.org/blog/2016-09-01-this-blog-is-now-behind-cloudflare.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>Back in July I registered the domain <a href="http://zhimingwang.org">zhimingwang.org</a> and pointed this GitHub Pages-powered blog at it. Since then I have lost the HTTPS badge due to GitHub Pages not supporting HTTPS on custom domains (see <a href="https://github.com/isaacs/github/issues/156">isaacs/github#156</a>).</p>
<p>There have been a lot of discussions on isaacs/github#156 (and stupid <a href="http://archive.zhimingwang.org/blog/2016-01-18-me-too-comments-on-github.html">+1's</a> too). Among the proposed solutions is putting the website behind CloudFlare. I carefully investigated <a href="https://blog.cloudflare.com/secure-and-fast-github-pages-with-cloudflare/">this option</a> and read almost all the arguments against it. I fully understand CloudFlare's SSL models (summarized in the image below), and I do realize most if not all of the limitations of CloudFlare, including CloudFlare being a huge MITM (which is inevitable for a CDN anyway), as well as most if not all of its annoyances, including CAPTCHAs which I myself would occasionally run into when I'm browsing with PIA VPN, and JavaScript-based browser checks.</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160901-cloudflare-ssl-modes.png" target="_blank"><img alt="CloudFlare's SSL modes. I use the Full SSL mode so that both ends of the connection are encrypted. Again, I know CloudFlare is a big MITM and could be a high profile target. Credit: CloudFlare." src="http://archive.zhimingwang.org/img/20160901-cloudflare-ssl-modes.png" width="500"/></a>
<p class="caption">CloudFlare's SSL modes. I use the Full SSL mode so that both ends of the connection are encrypted. Again, I know CloudFlare is a big MITM and could be a high profile target. Credit: <a href="https://blog.cloudflare.com/secure-and-fast-github-pages-with-cloudflare/">CloudFlare</a>.</p>
</div>
<p>After careful evaluation, I decided that CloudFlare's SSL model is good enough for me. After all, this is just a damn blog, with nothing sensitive. TLS is still nice because it guards against prying eyes and unethical ad-injecting ISPs or Wi-Fi hotspots, but other than that, it isn't necessary.</p>
<p>End result: this blog is now behind CloudFlare. Readers should now see that green HTTPS badge again (note that I'm enforcing HTTPS — without HSTS though). As for CAPTCHAs, I have adjusted the firewall settings on CloudFlare's dashboard — "Security Level" to "Essentially Off" and "Challenge Passage" to 1 year, so hopefully it won't be too annoying.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-09-01-this-blog-is-now-behind-cloudflare.html#fn1" id="fnref1"><sup>1</sup></a></p>
<p><strong>09/01/2016 Update.</strong> I just realized that <a href="https://support.cloudflare.com/hc/en-us/articles/203306930-Does-CloudFlare-block-Tor-">CloudFlare supports whitelisting Tor traffic</a>. Did that.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>I don't use Tor, and don't intend to raise Big Brother's suspicion by using it, so I have no idea of the actual Tor experience.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-09-01-this-blog-is-now-behind-cloudflare.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">It's 2016, and Microsoft is the only legit player who spams me without unsubscribe links</title><link href="http://archive.zhimingwang.org/blog/2016-06-24-its-2016-and-microsoft-is-the-only-legit-player-who-spams-me-without-unsubscribe-links.html"/><updated>2016-06-24T05:40:01+08:00</updated><id>http://archive.zhimingwang.org/blog/2016-06-24-its-2016-and-microsoft-is-the-only-legit-player-who-spams-me-without-unsubscribe-links.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I'm so tired of Microsoft spam. Microsoft is known for being intrusive accross the board, and their newsletters are no different: I literally can't name one legit company in this day and age who doesn't put unsubscribe links in newsletters.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-06-24-its-2016-and-microsoft-is-the-only-legit-player-who-spams-me-without-unsubscribe-links.html#fn1" id="fnref1"><sup>1</sup></a> I get "Azure pricing and services updates" newsletters all the time, as well as "exciting news" from Windows Insider Program (which doesn't excite me at all) every once in a short while.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-06-24-its-2016-and-microsoft-is-the-only-legit-player-who-spams-me-without-unsubscribe-links.html#fn2" id="fnref2"><sup>2</sup></a> I still occasionally receive random Chinese language promotions of Windows, presumably because I used a Windows Phone as my secondary phone for three months back home in the summer of 2013 (which was a horrible experience). Why Microsoft hasn't been regulated for spam yet, I do not know.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>To be fair, Amazon used to force promotional email on student members, but (if memory serves) I haven't seen one in ages.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-06-24-its-2016-and-microsoft-is-the-only-legit-player-who-spams-me-without-unsubscribe-links.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>"If you wish to stop receiving Windows Insider Program emails, you will need to leave the program." I guess that's the price of downloading a couple of Windows 10 insider builds.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-06-24-its-2016-and-microsoft-is-the-only-legit-player-who-spams-me-without-unsubscribe-links.html#fnref2">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Chrome is screwing with our extensions... Again</title><link href="http://archive.zhimingwang.org/blog/2016-05-07-chrome-is-screwing-with-our-extensions-again.html"/><updated>2016-05-07T18:49:26-07:00</updated><id>http://archive.zhimingwang.org/blog/2016-05-07-chrome-is-screwing-with-our-extensions-again.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>Chrome is growing more and more hostile by the day. See <a href="http://archive.zhimingwang.org/blog/2016-03-06-google-chrome-keeps-getting-uglier.html">Google Chrome keeps getting uglier</a> for an earlier take. What I didn't report in the earlier post is that not only can't you show/hide extension buttons as easily as before, you can't even control which buttons appear in the toolbar anymore — they come and go as they wish.</p>
<p>As if screwing the app icon, extension buttons and the overall design is not enough, now they have upped their game again. I'm running Chrome 50.0.2661.94 from April 28, and I just rebooted my machine only to be greeted with a fleeting "unsupported extensions" (or something like that) message as I launched Chrome. I digged into the extensions page, and guess what, all sideloaded extensions (except unpacked ones) have been disabled.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-05-07-chrome-is-screwing-with-our-extensions-again.html#fn1" id="fnref1"><sup>1</sup></a> This in itself may not be too surprising (Google took away sideloaded extensions from non-developers last year), except that <strong>the "enable" button, which used to work at least in developer mode, doesn't function anymore.</strong> The message is very stupid:</p>
<blockquote>
<p>This extension is not listed in the Chrome Web Store and <strong>may</strong> have been added without your knowledge.</p>
</blockquote>
<p>Bold by me. Okay, so what if they have been added <strong>with my knowledge</strong>? No way to enable legit extensions (some written by none other than myself) just because of a "may"? Here's the <a href="https://support.google.com/chrome/answer/2811969">only migration path</a> they offer, by the way:</p>
<blockquote>
<p>If you need to use a disabled extension, you can contact the extension's developer and ask them to upload their extension to the Chrome Web Store.</p>
</blockquote>
<p>Seriously? Do they honestly think Chrome Web Store serves everyone's needs? First, they have every right to refuse or take down any extension in their store. This is dangerous. What if one day they conclude that Adblock Plus is hurting their ad revenue too much and decide to take it down? Secondly, people may not want to make every extension publicly available. For instance, I have some personal extensions that I have developed on my dev machine, packaged into .crx, and installed on other machines. Some of these are publicly available (on GitHub), and others are not. It's not hard to conclude that other people may have private extensions too, and there may be extensions that are only available in some private circles. Now, people have to load unpacked extensions, which is much easier to screw up for regular folks, or they're out of luck.</p>
<p>To add insult to injury, every time I launch Chrome now, I'm greeted by this "Disable Developer Mode Extensions" message:</p>
<blockquote>
<p>Extensions running in developer mode can harm your computer. If you're not a developer, you should disable these extensions running in developer mode to stay safe.</p>
</blockquote>
<p>As if there're not enough malicious extensions in the Chrome Web Store, let alone crap. May I tell Chrome that I <em>am</em> a developer and ask it to shut up? Apparently no.</p>
<p>With the current trend in Chrome, I might want to switch to Opera again.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-05-07-chrome-is-screwing-with-our-extensions-again.html#fn2" id="fnref2"><sup>2</sup></a> The only thing preventing me from doing so right now is their new <a href="https://i.imgur.com/F8Tc67m.png">horrendous-looking fat icon</a>. However, Chrome has also destroyed their icon and I need to <a href="https://github.com/zmwangx/fileicon">replace it</a> after every update anyway, so I might as well do the same thing for Opera. We'll see.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>I'm not sure why they weren't disabled upon first launch after the update, but given the randomness of extension buttons in my toolbar with hardly any action on my part, I won't be surprised if I were told that they had messed up the extension system completely.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-05-07-chrome-is-screwing-with-our-extensions-again.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>Modern day Safari is also pretty nice, and the team is showing great attitude lately, with Safari Technology Preview and tweets like <a href="https://twitter.com/webkit/status/725725657548738561">this one</a>, for instance. However, the lack of extensions is a big road block, and the fact that the used-to-be-free Safari Developer Program has been incorporated into the $99/yr Apple Developer Program certainly doesn't help. (I used to be a member. Now I've been kicked out.)</p>
<p>Note that Safari is more locked down in a sense compared to OS X and iOS. On OS X you can apparently run unsigned software; on iOS 9 and later you can create personal provisioning profiles with just an Apple ID. Neither is true for Safari extensions, which still require a signing key from developer program membership. I wonder if Apple will introduce free keys for personal use on Safari, too.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-05-07-chrome-is-screwing-with-our-extensions-again.html#fnref2">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Emacs's got a redesigned website!</title><link href="http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html"/><updated>2016-04-10T03:04:19-07:00</updated><id>http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I've been very busy lately, so I haven't posted anything for a month. As a result, I have many topic notes sitting in Notes.app — including the kik-left-pad-npm drama, the Text Expander outcry, and such — waiting to be organized and written up (I'll probably never write them up in the end, since these days it's very weird to write an opinion piece about an event whose attention span has already lapsed).</p>
<p>Anyway, this will be a short post about Emacs's <a href="https://gnu.org/software/emacs/">redesigned website</a>. See screenshot at the end. Apparently this was <a href="https://twitter.com/NicolasPetton/status/714854524108800000">last week's news</a>, but there's little interest in Emacs in general, so the news only reached me two days ago, sort of by chance.</p>
<p>According my impression, Emacs has been the underdog for quite some time. First, when you compare to vi/Vim, hard stats shed light on popularity: there are way more VimL repos than Elisp ones on GitHub.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html#fn1" id="fnref1"><sup>1</sup></a> Also, there are more exciting (or at least exciting-sounding or excitement-inducing) things happening in the Vim realm, e.g. Neovim, but not so much in the Emacs kingdom (excuse me if I missed something big). I'm actually very curious how Vim sold itself to so many people. I, for one, can't tolerate the Esc key at all (yes, I know basic editing in Vim, and I know the various workarounds to Esc, some of them reasonable and some not). I can't understand how people could laugh at Escape Meta Alt Control Shift — oh, I never used Esc in Emacs once, by the way — when the single most awkward Esc key serves a fundamental purpose by default in their own beloved editor. The Esc key is of course not my only gripe with Vim, nor the biggest; I'll however stop here to avoid turing this post into a complaint about Vim. Apart from Vim, Emacs is also being sidelined by more modern GUI-based text editors like Atom,<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html#fn2" id="fnref2"><sup>2</sup></a> or various IDEs. Atom <a href="http://blog.atom.io/2016/03/28/atom-reaches-1m-users.html">recently</a> reached one million monthly active users. I actually like certain parts of Atom a lot, e.g. the project navigation sidebar,<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html#fn3" id="fnref3"><sup>3</sup></a> but I simply can't give up my good ol' tty.</p>
<p>Personal preferences aside, I think Emacs does need a bit more publicity to draw a few more users. Whether redesigning the website will help at all I don't know; maybe the effect will be statistically indistinguishable from zero, but the bottom line is that people like pretty websites, so why not. The redesigned homepage is a bit more graphics-heavy, but it currently weighs a total of 521.33KB — within the tolerable range.</p>
<p>The most interesting thing I found on the redesigned homepage is the link to <a href="http://emacsrocks.com">emacsrocks.com</a>. I aimlessly clicked on the last episode — <a href="http://emacsrocks.com/e15.html">episode 15</a> — just to see what it was like, and ended up astonished. The episode is about <a href="https://github.com/pashky/restclient.el"><code>restclient.el</code></a>, which turned out to be wicked cool. In the real world it's probably a little bit too geeky to my liking, and I use the more mundane (and more powerful) <a href="https://luckymarmot.com/paw">Paw</a> as my REST client, but I can't stop admiring the beauty of <code>restclient-mode</code>. I'll definitely find time to watch all episodes of Emacs Rocks, and you probably should, too.</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160409-emacs-website-screenshot-half-size.png" target="_blank"><img alt="A scaled down screenshot of the redesigned gnu.org/software/emacs. Full screenshot on my 2880x1800 MBP is here. Actually I lied a bit — the screenshots were taken with pageres, so I could have specified any resolution." src="http://archive.zhimingwang.org/img/20160409-emacs-website-screenshot-half-size.png" width="720"/></a>
<p class="caption">A scaled down screenshot of the redesigned <a href="https://gnu.org/software/emacs/">gnu.org/software/emacs</a>. Full screenshot on my 2880x1800 MBP is <a href="http://archive.zhimingwang.org/img/20160409-emacs-website-screenshot.png">here</a>. Actually I lied a bit — the screenshots were taken with <a href="https://github.com/sindresorhus/pageres"><code>pageres</code></a>, so I could have specified any resolution.</p>
</div>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>According to <a href="http://githut.info/">GitHut</a>, in 2014 Q4, there were 22,450 VimL and 9,978 Elisp repositories on GitHub, respectively. And according to a real time search I did just now, the <a href="https://github.com/search?q=language%3AVimL">VimL number</a> has risen to 82,519 and the <a href="https://github.com/search?q=language%3AElisp">Elisp number</a> to 30,320. The ratio has risen from 2.25:1 to 2.72:1. To add insult to injury, on GitHub's <a href="https://github.com/search/advanced">advanced search page</a>, GitHub lists VimL in the "Popular" language section and Elisp in "Everything else". Hurt feelings anyone?<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>Of course Emacs can operate in standalone GUI mode (or more precisely, window system mode), and more can be done in GUI mode (both in terms of customizability and functinality). However, in my early days with Emacs I found the GUI look like crap — the default always does, even to this day. I can never bring myself to use anything crappy-looking, unless I've got no choice, so I went with the TUI. Later I learned how to make the GUI habitable (still not as nice as the uniformity I find in tty, though), but by that time I'm already totally in love with tty mode and probably will never switch.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html#fnref2">↩︎</a></p></li>
<li id="fn3"><p>In Emacs I have ido, fiplr and sr-speedbar to help with navigation, but this is one area where a graphical sidebar really shines.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-04-10-emacss-got-a-redesigned-website.html#fnref3">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Google Chrome keeps getting uglier</title><link href="http://archive.zhimingwang.org/blog/2016-03-06-google-chrome-keeps-getting-uglier.html"/><updated>2016-03-06T14:59:45-08:00</updated><id>http://archive.zhimingwang.org/blog/2016-03-06-google-chrome-keeps-getting-uglier.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I hate to say this, but the Google Chrome team keeps making poor design decisions to make it more and more ugly. I still remember the sad day when the kind of cool wrench button gave way to the utterly boring hamburger one. I also remember the sad day when the omnibox dropdown <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=276746">pointlessly went full width</a> (after more than two years, I still fail to see how it makes any sense, although my eyes have long grown used to it).<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-03-06-google-chrome-keeps-getting-uglier.html#fn1" id="fnref1"><sup>1</sup></a> And I'm sure there are other stupid changes that I can't name at the moment.</p>
<p>Unfortunately, they just won't stop. <a href="http://googlechromereleases.blogspot.com/2016/03/stable-channel-update.html">Four days ago</a> stable 49.0.2623.75 came out with a flurry of horrible visual changes.</p>
<ol style="list-style-type: decimal">
<li><p>The icon. For whatever reason I have the impression that I might have seen this a while ago, but let's just pretend it's brand new. The new app icon is the most outrageously flat icon I've even seen. Compare it to that of 48.0.2564.103<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-03-06-google-chrome-keeps-getting-uglier.html#fn2" id="fnref2"><sup>2</sup></a>:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-49.0.2623.75-icons-side-by-side.png" target="_blank"><img alt="Old and new app icons side by side. To the left, the 48.0.2564.103 icon; to the right, the 49.0.2623.75 icon." src="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-49.0.2623.75-icons-side-by-side.png" width="512"/></a>
<p class="caption">Old and new app icons side by side. To the left, <a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-icon.png">the 48.0.2564.103 icon</a>; to the right, <a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-49.0.2623.75-icon.png">the 49.0.2623.75 icon</a>.</p>
</div>
<p>And let's see them in action:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-49.0.2623.75-icons-side-by-side-in-dock.png" target="_blank"><img alt="Both icons in the dock, old one the left and new one on the right." src="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-49.0.2623.75-icons-side-by-side-in-dock.png" width="560"/></a>
<p class="caption">Both icons in the dock, old one the left and new one on the right.</p>
</div>
<p>Apart from flatness (lack of any gloss found in almost all Apple icons, however flattened they are), see how the new icon is notably larger than the old one, and any other circular icons for that matter. Apparently, consistency and guidelines mean nothing to them.</p>
<p>I wonder why they made this change. Maybe for material design? I certainly don't want to see my Mac infested by material design, thank you. And maybe to keep the icon in line with their new Google branding? Indeed, just like <a href="https://web.archive.org/web/20160306221914/https://g-design.storage.googleapis.com/production/v5/assets/g-logo.png">the new Google logo</a> which did away with serifs, this one has no depth at all and is very childish.</p>
<p>It's a shame I can't just throw this icon out of my dock. Looks like in addition to iTunes now I have yet another icon to replace following each update, except this one updates much more often, and almost silently.</p></li>
<li><p>Downloads. I almost thought I was hacked when I opened the Downloads tab and saw</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-49.0.2623.75-downloads.png" target="_blank"><img alt="Downloads in 49.0.2623.75." src="http://archive.zhimingwang.org/img/20160306-chrome-mac-49.0.2623.75-downloads.png" width="848"/></a>
<p class="caption">Downloads in 49.0.2623.75.</p>
</div>
<p>instead of a nice and clean</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-downloads.png" target="_blank"><img alt="Downloads in 48.0.2564.103." src="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-downloads.png" width="728"/></a>
<p class="caption">Downloads in 48.0.2564.103.</p>
</div>
<p>Materail design infestation, apparently. Funny how they managed to convey less info in a LOT more space, and look horrible at the same time. At least they can choose a pleasant color palette if they want to use color (which is totally unnecessary as seen from the old design)? No, they can't.</p></li>
<li><p>Incognito mode. There's a reason why books are printed on light-colored paper, and there's a reason why the web is predominantly light-backgrounded, including user agent default style sheets. The old incognito follows the light background rule, plus a non-intrusive notice in the middle and a reasonably shaded tab bar to indicate incognito status:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-incognito.png" target="_blank"><img alt="Incognito window in 48.0.2564.103." src="http://archive.zhimingwang.org/img/20160306-chrome-mac-48.0.2564.103-incognito.png" width="893"/></a>
<p class="caption">Incognito window in 48.0.2564.103.</p>
</div>
<p>But not anymore. Since those of you using Incognito mode must be conducting shady business, why not highlight that with a black background:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160306-chrome-mac-49.0.2623.75-incognito.png" target="_blank"><img alt="Incognito window in 49.0.2623.75. Even more shocking if you maximize your browser windows." src="http://archive.zhimingwang.org/img/20160306-chrome-mac-49.0.2623.75-incognito.png" width="625"/></a>
<p class="caption">Incognito window in 49.0.2623.75. Even more shocking if you maximize your browser windows.</p>
</div>
<p>Oh. My. God. Now I hesitate whenever I want to press ⇧⌘N; it's just too great a cultural shock for me to handle.</p></li>
</ol>
<p>Those are just three changes I've discovered so far. Hopefully there are no more lurking surprises.</p>
<p>Conclusion? Sigh.</p>
<hr/>
<p><strong>03/09/2016 update</strong>. They also broke showing/hiding extension buttons (from toolbar) recently, probably in the same update. We used to be able to reshow a hidden button from <code>chrome://extensions</code>; that's no longer possible. Now we need to click on the hamburger (great), right click on one of the hidden buttons — which temporarily promotes the button to the toolbar and display the context menu, and while the context menu is still on, click on "Keep in Toolbar". So intuitive, your average computer users are definitely going to figure that out by themselves. Very nice.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>Dev left a <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=276746#c28">comment</a> when marking that issue as wont fix:</p>
<blockquote>
<p>... The current look is a precursor to a family of related work to make the Omnibox better, so expect to see more investment in this space to come. ...</p>
</blockquote>
<p>A family of related work? After 2.5 years the omnibox looks almost exactly the same as the screenshot in the issue. More to come my ass. Maybe I should be grateful, at least it didn't get worse.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-03-06-google-chrome-keeps-getting-uglier.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>I realized the last stable was 48.0.2564.109 instead of 48.0.2564.103 only after taking the screenshots. Doesn't matter anyway.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-03-06-google-chrome-keeps-getting-uglier.html#fnref2">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Dropbox, Noteworthy, and damned skeuomorphism</title><link href="http://archive.zhimingwang.org/blog/2016-01-26-dropbox-noteworthy-and-damned-skeuomorphism.html"/><updated>2016-01-26T12:18:36-08:00</updated><id>http://archive.zhimingwang.org/blog/2016-01-26-dropbox-noteworthy-and-damned-skeuomorphism.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I just opened a note in a PDF within Dropbox's iOS app (never done that before), and instead of readable text what I saw was basically spaghetti:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160126-dropbox-noteworthy.png" target="_blank"><img alt="A PDF note in Dropbox iOS. Noteworthy (scream). I know there's a typo, by the way." src="http://archive.zhimingwang.org/img/20160126-dropbox-noteworthy.png" width="300"/></a>
<p class="caption">A PDF note in Dropbox iOS. Noteworthy (scream). I know there's a typo, by the way.</p>
</div>
<p>That font is unmistakably Noteworthy, the default font in Apple's Notes app in Mountain Lion, when Apple was still practicing the damned skeuomorphism. (In case you can't recall how it looked like, let me point you to <a href="http://arstechnica.com/apple/2012/07/os-x-10-8/7/#notes">the John Siracusa review</a> for screenshots.) Just like your coworker's average handwritten notes, it is hardly legible and takes tremendous effort just to decode, especially when clustered in a paragraph rather than a short one-liner. Compare that to the same note, legibly rendered in Helvetica in PDF Expert:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160126-pdf-expert-note.png" target="_blank"><img alt="The same note (typo corrected) in PDF Expert Mac." src="http://archive.zhimingwang.org/img/20160126-pdf-expert-note.png" width="258"/></a>
<p class="caption">The same note (typo corrected) in PDF Expert Mac.</p>
</div>
<p>This is an example of sacrificing usability for design aesthetics (an old-fashioned one for that matter, and an abonimable one if you ask for my opinion). Hard to believe we can still see it in 2016, from an otherwise great developer that is Dropbox.</p>
]]></content></entry><entry><title type="html">Antivirus app on MAS top chart?</title><link href="http://archive.zhimingwang.org/blog/2016-01-24-antivirus-app-on-mas-top-chart.html"/><updated>2016-01-24T18:43:28-08:00</updated><id>http://archive.zhimingwang.org/blog/2016-01-24-antivirus-app-on-mas-top-chart.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>Today for whatever reason I clicked on MAS's "Top Charts" page, and was immediately in for a surprise. Next to our great friend 1Password is an app called "AntiVirus Sentinel Pro", which sells for $9.99:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160124-mas-top-paid.png" target="_blank"><img alt="AntiVirus Sentinel "Pro". These days many people like to end their apps' names with "Pro", even when there's nothing pro about them. This "Pro" app, for instance, comes from a developer whose three out of four apps matches the regex ^([A-Z](a-z)+ )+Pro$, and despite their names they are definitely geared towards uninformed newbies." src="http://archive.zhimingwang.org/img/20160124-mas-top-paid.png"/></a>
<p class="caption">AntiVirus Sentinel "Pro". These days many people like to end their apps' names with "Pro", even when there's nothing pro about them. This "Pro" app, for instance, comes from a developer whose three out of four apps matches the regex <code>^([A-Z](a-z)+ )+Pro$</code>, and despite their names they are definitely geared towards uninformed newbies.</p>
</div>
<p>The first rule of using MAS (or any kind of app store, for that matter) is that you research MAS apps outside the MAS. So let's Google "AntiVirus Sentinel Pro"... First result: <a href="https://discussions.apple.com/thread/6749331">Apple Support forum thread</a> from late 2014, "Is AntiVirus Sentinel Pro legit? If not, how can I delete it?" Good question about any AV product. However, you'll immediately find it hilarious when you read on:</p>
<blockquote>
<p>I have purchased and downloaded AntiVirus Sentinel Pro for Macintosh with Yosemite OS. I have a bad feeling this application is useless and maybe even harmful. Anyone knows if it is safe to use it?</p>
</blockquote>
<p>Okay, so why did you purchase it in the first place? I guess clueless users like this one are in every MAS developer's wet dreams.</p>
<p>Let's continue with the Google search results. Second one is the iTunes preview link. Third one is a <a href="http://www.securityfocus.eu/www/sentinel-pro.html">YouTube video</a> (also linked from MAS) which seems to be the only online documentation this app's got. Judging from the video the interface seems to be done in Java or something... Never mind that. Fourth result is a <a href="http://www.mac-forums.com/os-x-operating-system/327356-antivirus-sentinel-pro.html">rather recent thread</a> (August 2015) from Mac Forums. Not this again:</p>
<blockquote>
<p>I am a new Mac user. Can somebody answer these questions? Somehow it appears I have installed AntiVirus Sentinel Pro. What is this? Is it a real software? Should I keep it or try to uninstall it?</p>
</blockquote>
<p>The fifth and sixth results are general OS X AV product reviews that don't even mention this app. The next three results are from MAS aggregation sites. The last result on the first page is the app's product page on MacUpdate (now apparently abandoned), with a shiny 0.5/5 stars (note the zero point) badge.</p>
<p>Now that we've finished the first page (and the results are not a bit reassuring), the question comes: where the heck is this app's home page? It's also not on the second page, actually. There's something interesting on the third (still no home page), that is <a href="https://twitter.com/claud_xiao/status/681981763938197504">this tweet</a>:</p>
<blockquote>
<p>$10 "AntiVirus Sentinel Pro" got top2 in US Mac App Store and top1 in 48 countries--but it's just ClamAV+AdwareMedic signs+3 bullshit signs.</p>
</blockquote>
<p>Hmm. You might want to read Thomas Reed's (known for The Safe Mac) responses from that thread.</p>
<p>Anyway, we were sidetracked. Back to the home page, actually this app does have one, but it's just <a href="http://www.securityfocus.eu/www/sentinel-pro.html">a single page</a>, which simply states some marketing bullshit and directs to MAS (where the same bullshit is repeated). Seriously? That's the best you can do for your "pro" app, especially a security-related one?</p>
<p>Back to MAS, the reviews are kind of jokes, too:</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20160124-antivirus-sentinel-pro-reviews.png" target="_blank"><img alt="Stupid reviews for stupid app." src="http://archive.zhimingwang.org/img/20160124-antivirus-sentinel-pro-reviews.png"/></a>
<p class="caption">Stupid reviews for stupid app.</p>
</div>
<p>The first one begins with</p>
<blockquote>
<p>Just switched to Apple from Windows ... and researched anti-virus software.</p>
</blockquote>
<p>Don't really need to read further.</p>
<p>Second one, speaking of customer service:</p>
<blockquote>
<p>... it only took one email to him explaining my problem... had a patch up and ready on the app store to download within hours.</p>
</blockquote>
<p>This review is from October 2015. Since when was MAS so efficient? Why do I (and everyone else keeping tabs on Apple stuff) keep hearing stories like <a href="http://mjtsai.com/blog/2015/12/01/sketch-leaving-the-mac-app-store/">bug fix updates waiting for review after 59 days</a>?</p>
<p>The pattern goes on. By the way, how does this app keep track of all disk and network activity when itself is running in a sandbox? No idea (maybe I'm misunderstanding sandboxing).</p>
<p>In summary, even as an AV product, this one seems untrustworthy. Not to mention AV products on the Mac are generally superfluous if not harmful.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-01-24-antivirus-app-on-mas-top-chart.html#fn1" id="fnref1"><sup>1</sup></a> What people really need to learn is to practice safe browsing habits and to properly use content blockers, which AV product vendors and (intrusive-)ad-supported websites (that is most, commercial websites today) won't tell you because they would go out of business if they do.</p>
<p>And how this app got onto the top charts, that is a real mystery.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>Disclaimer: I personally have used ClamXav and AdwareMedic (which has since been bought by Malwarebytes) before to help my social-engineered friend (tech support scam, in case you ask). Just to scan their documents though. It is my belief that once you're pwned (even slightly), clean system reinstall is the only way to go, despite what AV products might tell you. In addition, I don't use AV products myself (except Microsoft Security Essentials on Windows); as a programmer I've had enough bad experience with AV blocking <em>my</em> programs back in the Windows days.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-01-24-antivirus-app-on-mas-top-chart.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Me-too comments on GitHub</title><link href="http://archive.zhimingwang.org/blog/2016-01-18-me-too-comments-on-github.html"/><updated>2016-01-18T16:36:40-08:00</updated><id>http://archive.zhimingwang.org/blog/2016-01-18-me-too-comments-on-github.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I frequently subscribe to issues on GitHub, be it bugs I want to see fixed or features I would like to see implemented. Then every once in a short while I get an email notification about one of those obnoxious "me too" or "+1" comments, by which I mean terse comments with little to no content other than "me too" or "+1" or some other variant bearing the same meaning.</p>
<p>Me-too comments under bug reports are the most untolerable. If you have more details regarding the issue (e.g., a more reliable reproducer) or insights into what's really going on, then by any means post them. On the other hand, if you can't provide anything helpful, then just keep your mouth shut, and quietly press "subscribe" if you would like to be kept posted. Posting a me-too comment adds nothing to the discussion, does not expedite the resolution a tiny bit, and only serves to annoy all parties involved.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-01-18-me-too-comments-on-github.html#fn1" id="fnref1"><sup>1</sup></a> As always, submit a patch if you're dissatisfied with the progress. Keep in mind that no one is obligated to fix bugs for you in FOSS.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-01-18-me-too-comments-on-github.html#fn2" id="fnref2"><sup>2</sup></a></p>
<p>Me-too comments under feature requests are more understandable, though I genuinely doubt that two or three people requesting a feature instead of one would make a big difference. After all, the issue tracker is not a feature voting platform; most folks understand this and behave themselves, so "me-too demand" isn't even remotely accurate at reflecting demand.</p>
<p>Me-too folks: please stop being childish. If you have nothing to add, don't add anything (unless otherwise requested).</p>
<hr/>
<p><strong>01/20/2015 Update.</strong> I came accross <a href="https://github.com/dear-github/dear-github">dear-githuub/dear-github</a> just now, which was started a mere six days ago, and the <a href="https://github.com/dear-github/dear-github/blob/bc7a4f6bc78445905751061bf96f731edda14c25/README.md">open letter</a> of which also places +1 comments on its list of biggest problems on GitHub.</p>
<hr/>
<p><strong>03/10/2015 Update.</strong> GitHub is finally reacting. See <a href="https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments">Add Reactions to Pull Requests, Issues, and Comments</a>.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>There are exceptional cases.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-01-18-me-too-comments-on-github.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>Here we're talking about the subset of FOSS that is also free as in beer.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-01-18-me-too-comments-on-github.html#fnref2">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">The dirtiest mistakes of OS X</title><link href="http://archive.zhimingwang.org/blog/2016-01-14-the-dirtiest-mistakes-of-os-x.html"/><updated>2016-01-14T01:02:52-08:00</updated><id>http://archive.zhimingwang.org/blog/2016-01-14-the-dirtiest-mistakes-of-os-x.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I must have written about this elsewhere, but here are my top three:</p>
<ol style="list-style-type: decimal">
<li><p><code>.DS_Store</code>. Finder litters faster than one could clean up.</p></li>
<li><p>HFS+ NFD*.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-01-14-the-dirtiest-mistakes-of-os-x.html#fn1" id="fnref1"><sup>1</sup></a> Heard of the cursed encoding <code>UTF8-MAC</code>? Pure Evil. Culprit of tons of garbled text issues (especially cross platform ones) and probably most length miscalculation issues. Even Apple's Terminal.app can't do NFD right. I wonder how Korean users navigate their filesystems in terminal.</p></li>
<li><p>Plist XML. It's XML, but even worse.</p></li>
</ol>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>NFD with an asterisk, i.e., not even NFD. According to Apple in <a href="https://developer.apple.com/library/mac/qa/qa1173/_index.html">an old Technical Q&A</a>,</p>
<blockquote>
<p>The terms used in this Q&A, precomposed and decomposed, roughly correspond to Unicode Normal Forms C and D, respectively. However, most volume formats do not follow the exact specification for these normal forms. For example, HFS Plus (Mac OS Extended) uses a variant of Normal Form D in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through U+2FAFF are not decomposed (this avoids problems with round trip conversions from old Mac text encodings). It's likely that your volume format has similar oddities.</p>
</blockquote>
<p>They are conscious enough to call these oddities.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-01-14-the-dirtiest-mistakes-of-os-x.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Virtualenvs for everyone</title><link href="http://archive.zhimingwang.org/blog/2016-01-01-virtualenvs-for-everyone.html"/><updated>2016-01-01T22:21:14-08:00</updated><id>http://archive.zhimingwang.org/blog/2016-01-01-virtualenvs-for-everyone.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>Python distutils for the most part is rather pleasant to work with. That is, pleasant until you've accumulated so many packages that you eventually run into a clash of namespace, or a dependency conflict (or dependency hell as most would affectionately call it).<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2016-01-01-virtualenvs-for-everyone.html#fn1" id="fnref1"><sup>1</sup></a> In contrast, npm's approach to dependencies shuts out dependency hell completely, but it is so paranoid and costs so much duplication that I find it hard to appreciate unless necessary. Somewhere in between there's the virtualenv approach which I find most appealing for smallish projects — keep a single copy of each package in the dependency tree in a contained environment specific to the project at hand. This is how we debug Python projects, and it certainly also should be <em>the</em> way we run command line tools written in Python.</p>
<p>There's another reason I like virtualenvs. There are tons of problems associated with choosing between Python 2 and 3 — some projects are Python 2 only, some are instead Python 3, some claim to be compatible with both but actually present subtle problems when you use one instead of the other. However, without virtualenvs, there's only one <code>bin</code> — <code>/usr/local/bin</code> — and everything's competing for it. Most programs (especially ones with a typical <code>setup.py</code>) don't install a soft/hardlink with a helpful <code>2</code> or <code>3</code> suffix when installing executables, let alone detailed suffixes like <code>2.7</code> or <code>3.5</code>, so without probing into the shebangs you're never sure which version of Python you're running your program with, and as a result Python 2/3 (or even a point release)-specific bugs occur randomly. Virtualenvs solve the problem by allowing you to have as many bins (and includes, and libs) as you like.</p>
<p>Hence the title "virtualenvs for everyone". I would like to install each command line program written in Python into a separate virtualenv. The only issue is that apparently I don't want too many bins in my <code>$PATH</code>; to solve this issue, the executable bits of each project should be linked to a central place, for which I choose <code>$HOME/bin</code>. There could be as many symlinks as we like, so now we can have multiple links with increasing detailed version suffixes, e.g., <code>3</code>, <code>3.5</code>, <code>3.5.1</code>. Very nice.</p>
<p>This task could clearly be automated; the only slightly tricky bit is to programmatically figure out which scripts a project installs to <code>bin</code>. Luckily, for projects using <code>setuptools.setup</code>, we can simply spoof that function. Here's my <code>setuptools/__init__.py</code>:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="co">#!/usr/bin/env python3</span>
<span class="co">"""setuptools stubs.</span>
<span class="co">Here we only stubbed the symbols in setuptools.__all__. Hopefully that's</span>
<span class="co">enough (actually I can't remember seeing any setup.py using more than</span>
<span class="co">setup and find_packages).</span>
<span class="co">setup has been spoofed to print the names of scripts, console_scripts</span>
<span class="co">and gui_scripts defined in the arguments to setup. Some user-friendly</span>
<span class="co">messages are also printed to stderr.</span>
<span class="co">"""</span>
<span class="im">from</span> __future__ <span class="im">import</span> print_function
<span class="im">import</span> re
<span class="im">import</span> sys
<span class="im">import</span> os
__all__ <span class="op">=</span> [
<span class="st">'setup'</span>, <span class="st">'Distribution'</span>, <span class="st">'Feature'</span>, <span class="st">'Command'</span>, <span class="st">'Extension'</span>, <span class="st">'Require'</span>,
<span class="st">'find_packages'</span>
]
<span class="kw">def</span> setup(<span class="op">**</span>kwargs):
scripts <span class="op">=</span> [os.path.basename(script_path)
<span class="cf">for</span> script_path <span class="kw">in</span> kwargs.pop(<span class="st">'scripts'</span>, [])]
<span class="cf">if</span> scripts:
<span class="bu">print</span>(<span class="st">'scripts:</span><span class="ch">\n</span><span class="st"> - </span><span class="sc">%s</span><span class="st">'</span> <span class="op">%</span> <span class="st">'</span><span class="ch">\n</span><span class="st"> - '</span>.join(scripts), <span class="bu">file</span><span class="op">=</span>sys.stderr)
entry_points <span class="op">=</span> kwargs.pop(<span class="st">'entry_points'</span>, {})
<span class="cf">for</span> entry_point <span class="kw">in</span> [<span class="st">'console_scripts'</span>, <span class="st">'gui_scripts'</span>]:
extra_scripts <span class="op">=</span> [re.split(<span class="st">'(\s|=)'</span>, spec.strip())[<span class="dv">0</span>]
<span class="cf">for</span> spec <span class="kw">in</span> entry_points.pop(entry_point, [])]
<span class="cf">if</span> extra_scripts:
<span class="bu">print</span>(<span class="st">'</span><span class="sc">%s</span><span class="st">:</span><span class="ch">\n</span><span class="st"> - </span><span class="sc">%s</span><span class="st">'</span> <span class="op">%</span> (entry_point, <span class="st">'</span><span class="ch">\n</span><span class="st"> - '</span>.join(extra_scripts)),
<span class="bu">file</span><span class="op">=</span>sys.stderr)
scripts.extend(extra_scripts)
<span class="bu">print</span>(<span class="st">'</span><span class="ch">\n</span><span class="st">'</span>.join(<span class="bu">sorted</span>(scripts)))
<span class="kw">class</span> Distribution(<span class="bu">object</span>): <span class="cf">pass</span>
<span class="kw">class</span> Feature(<span class="bu">object</span>): <span class="cf">pass</span>
<span class="kw">class</span> Command(<span class="bu">object</span>): <span class="cf">pass</span>
<span class="kw">class</span> Extension(<span class="bu">object</span>): <span class="cf">pass</span>
<span class="kw">class</span> Require(<span class="bu">object</span>): <span class="cf">pass</span>
<span class="kw">def</span> find_packages(<span class="op">**</span>kwargs): <span class="cf">pass</span></code></pre></div>
<p>Now, let <code>$HERE</code> be the directory containing our fake <code>setuptools/</code>, and <code>$PROJECT_ROOT</code> be the project root directory containing <code>setup.py</code>. Run</p>
<div class="sourceCode"><pre class="sourceCode zsh"><code class="sourceCode zsh"><span class="ot">PYTHONPATH=$HERE</span>:<span class="ot">$PYTHONPATH</span> python <span class="ot">$PROJECT_ROOT</span>/setup.py</code></pre></div>
<p>and bam! We get the names of all scripts on stdout.</p>
<p>My full automation scripts, including the Zsh main function <code>virtual-install</code>, can be found in <a href="https://github.com/zmwangx/prezto/tree/master/modules/python/functions"><code>modules/python/functions</code> in zmwangx/prezto</a>. I'm not including it here because it uses some custom helper, and it's just too long (200+ lines, but not very sophisticated). Happy virtualenving!</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>In rare cases, even installing a single package could land you in trouble. The classical example is installing the <code>readme</code> package on a case-insensitive filesystem (e.g., the default mode of HFS+). "Unfortunately" <a href="https://bugs.python.org/issue24633">this</a> has been fixed.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2016-01-01-virtualenvs-for-everyone.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Catches when installing Windows 7 with Boot Camp</title><link href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html"/><updated>2015-12-29T15:09:16-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I was looking for a use for my retired Mid-2012 Non-Retina MacBook Pro 13''<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fn1" id="fnref1"><sup>1</sup></a>, and unsurprisingly I figured that I would turn it into a OS X-Windows dual boot for some occasional gaming. I'm a CnC fan (not hardcore, but still), mainly for RA2/YR and TW/KW, and playing these inside Fusion is really a subsubpar experience. Due to the age of these games and their compatibility problems on Windows 8 and higher<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fn2" id="fnref2"><sup>2</sup></a>, I chose to shoot for a Windows 7 install.</p>
<p>Apple has a pretty thorough walkthrough in the support article <a href="https://support.apple.com/en-us/HT205016">Install Windows 7 and earlier on your Mac using Boot Camp</a>. There are, however, some catches that I would like to collect and share in this post.</p>
<ol style="list-style-type: decimal">
<li><p>Win 7 ISO isn't available for download in the appropriate language (given your product key). This one sounds incredibly stupid... But it is a real problem at least for me and several others (just Google). I have a valid Win 7 Ultimate license from my institution, so I went to <a class="uri" href="https://www.microsoft.com/en-us/software-download/windows7">https://www.microsoft.com/en-us/software-download/windows7</a> to grab my ISO (just for fun; I already have the image). However, after verifying my product key, here's the list of languages that I'm asked to choose from, where English is apparently missing (!!!):</p>
<div class="figure">
<a href="http://archive.zhimingwang.org/img/20151229-win7-iso-language-choice.png" target="_blank"><img alt="da !@#$?" src="http://archive.zhimingwang.org/img/20151229-win7-iso-language-choice.png" width="394"/></a>
<p class="caption">da !@#$?</p>
</div>
<p>I don't know the solution to this problem. In my case I've archived English Win 7 Ultimate SP1 images (both x86 and x64) before, so I just proceeded with my old image.</p></li>
<li><p>FileVault. It is my belief that FileVault needs to turned off before partitioning the drive with Boot Camp.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fn3" id="fnref3"><sup>3</sup></a></p></li>
<li><p><em>An error occured while partitioning the disk.</em> That's the unhelpful message from Boot Camp. If you try to manually partition the drive with Disk Utility, you'll probably get a much more helpful message like <em>Partition failed with the error: couldn't modify partition map because file system verification failed</em>. Now the problem is obvious, and the solution is simple. Boot to single user mode and repair the filesystem with <code>/sbin/fsck -fy</code>, or safer, <code>/sbin/fsck -f</code> which might require interaction.</p></li>
<li><p>During Windows installation you'll obviously be prompted to choose a system partition at some point, and due to Boot Camp only formatting to FAT32, you'll get the message <em>Windows cannot be installed to this hard disk space. Windows must be installed to a partition formatted as NTFS.</em> This one is easy, just click "Drive options (advanced)" then "Format", which automatically formats the partition to NTFS. This is actually documented in Apple's walkthrough, but mortals do panic in face of error messages, so let's also note it here.</p></li>
<li><p>Even after formatting the Boot Camp partition, it is still possible to get the error <em>Setup was unable to create a new system partition or locate an existing system partition</em>. It this happens, check if you have any USB drives (other than the installation media) plugged in. In my case my Time Capsule was plugged in, and rebooting with it unplugged fixed the problem. The exact cause of the problem is unclear to me. Some say it's due to Master Boot Record limiting the number of partitions to four, but why the heck is my external drive counted towards that limitation? I'd go for Win 7 installer is just confused. Anyway, just unplug anything that's not needed during Windows installation.</p></li>
</ol>
<p>Hopefully you're good after solving the aforementioned problems. If you followed Apple's walkthrough correctly, Boot Camp's setup.exe will be invoked automatically immediately after Windows finishes installation, and after a certain number of reboots your drivers will be up and running. Now you're ready to take control of your Windows. Install Chrome<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fn4" id="fnref4"><sup>4</sup></a> and Microsoft Security Essentials immediately, then hop right into the Windows Update hell to patch your four-year-old system. Of course, Windows Update being Windows Update won't be smooth — servers will be crowded as ever and just checking for updates will likely take forever, let alone downloads. After a semi-infinite amount of time you'll get your estimates (I got 212 updates). Click update and let Windows Update grind for hours. And wish yourself a good luck (that no update errors will occur — luckily I didn't get any).</p>
<p>By the way, the otherwise great Apple trackpad is almost unusable on Boot Camp Windows under any setting. I'm forced to use a mouse.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>2.9 GHz i7 + Intel HD Graphics 4000 + 16 GB RAM + frigging slow 750 GB 5400-rpm spinning disk I've yet to replace.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>RA2/YR used to have problems even on Windows 7, at least inside Fusion, so I used to play them in XP SP3 VMs; I've yet to try them with Windows 7 running on bare metal.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fnref2">↩︎</a></p></li>
<li id="fn3"><p>I'm not completely sure that this is necessary. I was greeted with partitioning errors initially which I thought was due to FileVault, so I switched it off (the actual process is much longer than "switching it off", since the whole disk has to be decrypted and rewritten), but as you'll see later, the partitioning errors were at least partly due to a slightly corrupt filesystem.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fnref3">↩︎</a></p></li>
<li id="fn4"><p>You can't even browse Microsoft's own websites with stock IE8. And IE11 is locked behind a hell lot of Windows Updates (even then it is crap). Doing Windows Update is like building up a tech tree.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-29-catches-when-installing-windows-7-with-boot-camp.html#fnref4">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Why I want lossless music on iTunes Music Store</title><link href="http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html"/><updated>2015-12-28T03:15:45-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>This is an impulse post after reading <a href="https://marco.org/2015/12/27/apple-hd-audio-again">"Apple again rumored to be working on high-resolution audio"</a>.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html#fn1" id="fnref1"><sup>1</sup></a></p>
<p>To be clear, I'm no audiophile. I can't tell the difference between 256kbps AAC and lossless (maybe not even the difference between 128k and 256k), and my midrange to lower midrange equipments probably won't let me tell anyway. I'm certainly not a consumer of snake oil.</p>
<p>However, I still prefer to get everything in lossless, simply because <strong>"good enough" today is almost never good enough tomorrow</strong>. Fifty years later I'm most likely still wandering this planet, I and my music collection. I would be extremely regretful if I didn't archive the highest quality versions of my favorite tracks today, only to find them inferior-sounding fifty years later, which is a pretty realistic possibility given how fast technology advances.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html#fn2" id="fnref2"><sup>2</sup></a> Even today's lossless could be inferior-sounding in the future, but there would be no regret.</p>
<p>To be extra clear, I'm talking about lossless for archival purposes, so what I want to see is a lossless download option in ITMS.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html#fn3" id="fnref3"><sup>3</sup></a> Streaming can be done in whatever good enough® sampling frequency and bitrate that's currently in use, since it's a one-off thing with no effects on tomorrow (and I don't give a shit about streaming and subscription anyway). Offering lossless downloads likely won't put much burden on Apple's infrastructure, since they already deliver much more bandwidth-demanding movies on the same channel. Moreover, albums on ITMS aren't much cheaper than physical CDs, while the cost is apparently lower than CD production, the audience apparently wider, and the chances of impulse purchases (especially of single tracks) much higher, so I would suppose such a move (delivering lossless on ITMS) won't considerably hurt record labels' profits either. After all, if they don't make it easy for consumers, many consumers will just pirate — it's way too easy to pirate music.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>And I did see the <a href="http://www.macrumors.com/2015/12/20/apple-high-resolution-audio/">MacRumors article</a> a week ago. I even registered a MacRumors account, which I never bothered to do, just to comment on that article... It just didn't occur to me to write a blog post at that time.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>You might be skeptical of my hearing when I'm in my seventies... But I could well be showing my favorites to someone with perfect hearing, say my grandchildren.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html#fnref2">↩︎</a></p></li>
<li id="fn3"><p>I know there are many online music stores that sell lossless music, but ITMS has the largest catalog in the world, and for many titles I care about, ITMS is still the only place in this country where I can make legal digital purchases.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-28-why-i-want-lossless-music-on-itunes-music-store.html#fnref3">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Lesson on magic method access of Python new-style classes (from my failed Python3 port of Tomorrow)</title><link href="http://archive.zhimingwang.org/blog/2015-12-27-lesson-on-magic-method-access-of-python-new-style-classes-from-my-failed-python3-port-of-tomorrow.html"/><updated>2015-12-27T16:47:05-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-12-27-lesson-on-magic-method-access-of-python-new-style-classes-from-my-failed-python3-port-of-tomorrow.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I know the title is formidably long, but I can't find something more accurate (and my homegrown mini CMS doesn't support subtitle), so please bear with me.</p>
<p>So, I have <a href="https://github.com/madisonmay/Tomorrow">madisonmay/Tomorrow</a> — "magic decorator syntax for asynchronous code in Python 2.7" — bookmarked for a long time<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-27-lesson-on-magic-method-access-of-python-new-style-classes-from-my-failed-python3-port-of-tomorrow.html#fn1" id="fnref1"><sup>1</sup></a> without ever trying it, because I simply don't write Python 2 code any more (except when I try to maintain compatibililty). I felt kind of strange that a ~50-line project with ~1000 stars on GitHub hasn't been ported to Python 3 already, so I gave it a shot just now.</p>
<p>I thought it would be easy:</p>
<ol style="list-style-type: decimal">
<li>Modernize the old-style class <code>Tomorrow</code>;</li>
<li>Replace <code>__getattr__</code> with <code>__getattribute__</code> for unconditional attribute routing, then make a few exceptions to prevent infinite recursion;</li>
<li><code>2to3</code> test cases;</li>
<li>Make meta changes, like removing the <code>futures</code> dependency.</li>
</ol>
<p>However, after doing 1–3, I ran the tests, and out of the five test cases, three failed and one errored. I tried to isolate the problem, and ended up with the following piece of proof-of-concept:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">class</span> PassThrough(<span class="bu">object</span>):
<span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>, obj):
<span class="va">self</span>._obj <span class="op">=</span> obj
<span class="kw">def</span> <span class="fu">__getattribute__</span>(<span class="va">self</span>, name):
<span class="cf">if</span> name <span class="op">==</span> <span class="st">"_obj"</span>:
<span class="cf">return</span> <span class="bu">object</span>.<span class="fu">__getattribute__</span>(<span class="va">self</span>, name)
<span class="bu">print</span>(<span class="st">"Accessing '</span><span class="sc">%s</span><span class="st">'"</span> <span class="op">%</span> name)
<span class="cf">return</span> <span class="va">self</span>._obj.<span class="fu">__getattribute__</span>(name)</code></pre></div>
<p>This snippet is valid in both Python 2.7 and Python 3, but here's the surprise:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="op">>>></span> g <span class="op">=</span> PassThrough(<span class="dv">0</span>)
<span class="op">>>></span> <span class="bu">print</span>(g)
<span class="op"><</span>__main__.PassThrough <span class="bu">object</span> at <span class="bn">0x10c662e48</span><span class="op">></span>
<span class="op">>>></span> <span class="bu">str</span>(g)
<span class="co">'<__main__.PassThrough object at 0x10c662e48>'</span>
<span class="op">>>></span> <span class="bu">hasattr</span>(g, <span class="st">'__str__'</span>)
Accessing <span class="st">'__str__'</span>
<span class="va">True</span>
<span class="op">>>></span> g.<span class="fu">__str__</span>()
Accessing <span class="st">'__str__'</span>
<span class="co">'0'</span></code></pre></div>
<p>In addition, here's what happens if you try to "pass through" a function:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="op">>>></span> <span class="kw">def</span> f(): <span class="cf">return</span> <span class="va">True</span>
<span class="op">>>></span> g <span class="op">=</span> PassThrough(f)
<span class="op">>>></span> g()
Accessing <span class="st">'__class__'</span>
Accessing <span class="st">'__class__'</span>
Traceback (most recent call last):
File <span class="st">"<ipython-input-6-d65ffd94a45c>"</span>, line <span class="dv">1</span>, <span class="kw">in</span> <span class="op"><</span>module<span class="op">></span>
g()
<span class="pp">TypeError</span>: <span class="st">'PassThrough'</span> <span class="bu">object</span> <span class="kw">is</span> <span class="kw">not</span> <span class="bu">callable</span>
<span class="op">>>></span> <span class="bu">callable</span>(g)
<span class="va">False</span>
<span class="op">>>></span> <span class="bu">hasattr</span>(g, <span class="st">'__call__'</span>)
Accessing <span class="st">'__call__'</span>
<span class="va">True</span>
<span class="op">>>></span> g.<span class="fu">__call__</span>()
Accessing <span class="st">'__call__'</span>
<span class="va">True</span></code></pre></div>
<p>As you can tell, although <code>__str__</code> or <code>__call__</code> may have been implemented through <code>__getattribute__</code>, and <code>hasattr</code> (which in turn depends on <code>getattr</code>) has no trouble finding them, they are not picked up by <code>str</code> or function call <code>(...)</code>. At this point, one would suspect that this is due to <code>str</code> or function call only looking at the class instance's <code>__dict__</code>. Compare this to the behavior of an old-style class:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="kw">class</span> PassThrough():
<span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>, obj):
<span class="va">self</span>._obj <span class="op">=</span> obj
<span class="kw">def</span> <span class="fu">__getattr__</span>(<span class="va">self</span>, name):
<span class="bu">print</span>(<span class="st">"Acessing '</span><span class="sc">%s</span><span class="st">'"</span> <span class="op">%</span> name)
<span class="cf">return</span> <span class="va">self</span>._obj.<span class="fu">__getattribute__</span>(name)</code></pre></div>
<p>Now:</p>
<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="op">>>></span> g <span class="op">=</span> PassThrough(<span class="dv">0</span>)
<span class="op">>>></span> <span class="bu">print</span>(g)
Acessing <span class="st">'__str__'</span>
<span class="dv">0</span>
<span class="op">>>></span> <span class="kw">def</span> f(): <span class="cf">return</span> <span class="va">True</span>
<span class="op">>>></span> g <span class="op">=</span> PassThrough(f)
<span class="op">>>></span> g()
Acessing <span class="st">'__call__'</span>
<span class="va">True</span></code></pre></div>
<p>Note that magic method access is always routed through <code>__getattr__</code>.</p>
<p>After some digging, my suspicion was confirmed: indeed, for new-style classes, rather than invoking <code>__getattribute__</code>, the Python interpreter only looks for magic methods in <code>__dict__</code>. But is there a workaround for implementing something like the <code>PassThrough</code> class above? There's a <a href="http://stackoverflow.com/a/9059858/">nice answer</a> on StackOverflow that uses a metaclass to "automatically add proxies for magic methods at the time of class creation", to quote the author. However, the thing about Tomorrow is that <em>we don't have the result and don't know whatever magic methods it might have at class creation</em> — after all, Python isn't a statically typed language. It is possible for programmers to offer hints, but then Tomorrow won't be as elegant and magical anymore. Therefore, unfortunately enough, Tomorrow isn't portable to Python 3 — at least not without a substantial hack that's beyond my knowledge, or a complete overhaul of its logic (haven't thought about that).</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>Pretty much since the beginning, I believe (the <a href="https://github.com/madisonmay/Tomorrow/commit/22a53dfbcf9b516ecd1770eeca9fcf1720271240">initial commit</a> was from July 24 of this year). I don't remember how I came accross it though.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-27-lesson-on-magic-method-access-of-python-new-style-classes-from-my-failed-python3-port-of-tomorrow.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">autoenv with auto cleanup</title><link href="http://archive.zhimingwang.org/blog/2015-12-26-autoenv-with-auto-cleanup.html"/><updated>2015-12-26T00:15:48-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-12-26-autoenv-with-auto-cleanup.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I heard about <a href="https://github.com/kennethreitz/autoenv">kennethreitz/autoenv</a> a long time ago. The idea of autoloading project-specific environment modifications is nice, but no auto cleanup after leaving a project was a showstopper for me.</p>
<p>Today, I took matters into my own hands and wrote a fresh Zsh implementation<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-26-autoenv-with-auto-cleanup.html#fn1" id="fnref1"><sup>1</sup></a> with auto cleanup support. Check it out: <a class="uri" href="https://github.com/zmwangx/prezto/tree/master/modules/autoenv">https://github.com/zmwangx/prezto/tree/master/modules/autoenv</a>.</p>
<p>As a quick promotion, let me show you two common examples.</p>
<p>First, inserting some local bin directory into the search path. This is easily done by a one-line <code>.env</code>, say,</p>
<div class="sourceCode"><pre class="sourceCode zsh"><code class="sourceCode zsh">autoenv-insert-paths bin libexec</code></pre></div>
<p>This way <code>$PWD/bin</code> and <code>$PWD/libexec</code> are inserted to the beginning of the search path, which will persist until you leave the directory tree. That is to say, the inserted paths will still be available when you descend into subdirectories (and more specific <code>.env</code>'s can even be stacked as you descend), but they will be purged as soon as you leave the tree. Clever, isn't it?</p>
<p>Secondly, exporting project-specific environment variables. The <code>.env</code> would look like</p>
<div class="sourceCode"><pre class="sourceCode zsh"><code class="sourceCode zsh"><span class="kw">export</span> <span class="ot">HOMEBREW_DEVELOPER=</span>not-for-the-faint-hearted
<span class="fu">autoenv-purge ()</span> <span class="kw">unset</span> HOMEBREW_DEVELOPER</code></pre></div>
<p>where the body of <code>autoenv-purge</code> will be executed when you leave the directory tree. No more junk floating around.</p>
<p>Again, for more info, including detailed usage and customization instructions, please visit <a href="https://github.com/zmwangx/prezto/tree/master/modules/autoenv"><code>modules/autoenv</code></a> in zmwangx/prezto.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>This is not a re-implementation in the common sense. My little Zsh module is inspired by kennethreitz/autoenv and reminiscent of that older project, but I took nothing from there (in fact I didn't even read their source code). I also don't claim to support their entire feature set. For instance, kennethreitz/autoenv claims to be Foreman compatible, which includes turning on <code>ALL_EXPORT</code>. However, I don't think <code>ALL_EXPORT</code> by default is a good idea, so with my <code>autoenv</code>, if you want <code>ALL_EXPORT</code> you have to set it explicitly.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-26-autoenv-with-auto-cleanup.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Regex flavor hell</title><link href="http://archive.zhimingwang.org/blog/2015-12-20-regex-flavor-hell.html"/><updated>2015-12-20T16:03:03-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-12-20-regex-flavor-hell.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I write a lot of shell scripts, which means dealing with common *ix utilities a lot. I typically want my scripts to work on both OS X and Linux (or OS X + GNU utilities, which is my personal setup), which means writing commands that are understood in both GNU/Linux and BSD worlds. Unfortunately that's not so simple, because to do that I usually have to give up readily available functionalities (especially the vast collection of useful options typical of GNU utilities) and am constantly thrown back to the stone age that is POSIX, or a little bit more than POSIX.</p>
<p>Working with regular expressions is especially painful. Almost every implementation of every utility (with regex support) has its own flavor of regex. Most notably the big three: <code>grep</code>, <code>sed</code> and <code>awk</code>. GNU utilities of course come with GNU extensions, but they are nothing when aiming for compatibility. Ignoring GNU extensions, there's a way to turn on standard POSIX extensions (ERE) on <code>sed</code>, but unfortunately GNU and BSD use different flags: <code>-r</code> for GNU sed and <code>-E</code> for BSD sed. The two implementations of <code>grep</code> thankfully use the same flag <code>-E</code> to turn on ERE, but GNU grep, being a GNU utility and having to distinguish itself from its mundane counterpart, further implements <code>-P,--perl-regexp</code> — regexers' dream. It's there but I can't use it, except in an interactive shell. <code>awk</code> has more than two implementations and will be left out of this discussion.</p>
<p>Anyway, despite all these flavor issues, I can usually get away with BRE, although it's verbose and unreadable as hell (quantifiers in particular) and doesn't support alternation. I would be thankful if BRE is the end of the story, but it is not. There are more tools lurking around trying to sabotage scripters. <code>find</code> is a perfect example. BSD <code>find</code>, unsurprisingly, uses BRE by default with <code>-regex</code> and <code>-iregex</code>, and ERE may be turned on with the <code>-E</code> flag. GNU findutils <code>find</code>, however, tries to be helpful and future-proof by having a <code>-regextype</code> option:</p>
<blockquote>
<p>Changes the regular expression syntax understood by <code>-regex</code> and <code>-iregex</code> tests which occur later on the command line. Currently-implemented types are <code>emacs</code> (this is the default), <code>posix-awk</code>, <code>posix-basic</code>, <code>posix-egrep</code> and <code>posix-extended</code>.</p>
</blockquote>
<p>The Emacs flavor? You mean Elisp regexp? Okay fine, <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html">BRE</a> — with few features other than grouping (<code>\(...\)</code>), quantifiers (<code>*</code> or <code>\{n,m\}</code>), bracket expressions and character classes — should still be pretty much compatible with Elisp regexp. However, the "Emacs flavor" isn't even the Elisp flavor. It's a <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/emacs-regular-expression-syntax.html">stripped version</a> specifically for findutils. In particular, there are <code>*</code>, <code>+</code> and <code>?</code> but no curly braces quantifiers, so gone is the dream of writing even just mildly complex regexps that are compatible with both BSD <code>find</code> and GNU findutils <code>find</code>. By the way, in case you wonder, the <a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html">POSIX <code>find</code></a> doesn't even have a <code>-regex</code> primary/operator...</p>
<p>What a cruelly realistic world we live in.</p>
]]></content></entry><entry><title type="html">Spoiled by Retina, in less than a day</title><link href="http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html"/><updated>2015-12-16T21:10:08-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>I finally got a 15'' Retina MacBook Pro this morning to replace my 13'' mid-2012 non-Retina MacBook Pro, whose spinning disk has been getting increasingly slower (or so I felt).<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html#fn1" id="fnref1"><sup>1</sup></a> Apparently this is a pretty significant landmark in my personal computing history, since I'm saying goodbye to both spinning disk and non-Retina display on my primary computing device.</p>
<p>The transition was initially smooth except for a few things. First, as a tap-to-click wizard I immediately turned on tap-to-click, but I had a hard time dragging things because it was too easy to trigger a force touch instead on the medium setting, and under the firm setting I could hardly force touch at all; in the end I just turned off force touch altogether, and haven't had any problem since. By the way, I was initially worried about the keyboard too but it worked surprisingly well for me, so no complaints there. Secondly, <a href="http://archive.zhimingwang.org/blog/2015-08-31-after-all-these-years-10pt-non-anti-aliased-monaco-is-still-the-best.html">10pt non-anti-aliased Monaco</a> looks weird on Retina since it's no longer the beloved bitmap version. I turned on antialiasing and now it's no longer weird, but it felt totally different and I'm not sure if I like it (definitely not as much as the 10pt bitmap Monaco anyway). It's okay right now but I'll probably need to spend some time trying out different fonts. Obviously there are <a href="https://news.ycombinator.com/item?id=10140728">like-minded folks</a> out there. Sad story.</p>
<p>So much for first impressions. Apart from Monaco, everything felt great, until I returned home (I was doing setup away from home to get a less shitty connection) and connected my 27'' external monitor. Holy crap, I couldn't believe my eyes. The dock icons — the first things I saw before launching anything — looked <em>so blurry I couldn't stare at them for more than a few seconds</em>. That was after staring at the Retina display for less than five hours. Not to mention PDFs; they look ultra crisp on the Retina display and ultra crappy on non-Retina — especially in Preview, which is a problem I've been aware of since Yosemite.<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html#fn2" id="fnref2"><sup>2</sup></a> Moreover, the terminal font is more problematic than initially estimated — <em>now I have a retina display and a non-retina one side-by-side, yet I can only set one font for my default profile, which will never satisfy both</em>!<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html#fn3" id="fnref3"><sup>3</sup></a> This is so awkward I can't think of a solution. One obvious approach is to ditch the blurry 27'' and only work from the Retina 15'', but should I really let the large canvas sit idle? No idea. Or should I get a 4K external display? First, a 4K display at 27'' still can't rival the pixel density of 2880x1800 at 15.4'' (Apple ships 5K at 27'' for a reason). Secondly and more importantly, I don't have the budget for such a thing after throwing money at an expensive 15'' rMBP (with 512 GB SSD)...</p>
<p>Transition periods are always awkward, I guess.</p>
<hr/>
<p><strong>12/17/2015 Update.</strong> After more than a full day's use, I actually quite love 10pt Monaco on a Retina display. I tried various fonts, including Menlo, Consolas and so on, but none of them has that whimsical feeling of Monaco. Hopefully the font is stuck now.</p>
<hr/>
<p><strong>12/28/2015 Update.</strong> A dozen days later, I can hardly look at 10pt Monaco on a non-Retina screen anymore, antialiased or not, especially not in bold. Mind blown.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>I haven't got the nerve to replace the hard drive myself, since it looks so much more complicated than upgrading the memory.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p>PDFs looked so horrible in Preview (and TeXShop, my LaTeX previewer, which only serves a niche) that I often viewed them in browsers (!!), where text at least looks reasonable (on par with slightly blurry text elsewhere). PDF Expert came along and kind of made the situation better for non-Retina.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html#fnref2">↩︎</a></p></li>
<li id="fn3"><p>Provided that I'll religiously stick to 10pt non-anti-aliased Monaco on non-Retina.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-16-spoiled-by-retina-in-less-than-a-day.html#fnref3">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Safeguarding git repos against accidental rm</title><link href="http://archive.zhimingwang.org/blog/2015-12-08-safeguarding-git-repos-against-accidental-rm.html"/><updated>2015-12-08T00:17:39-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-12-08-safeguarding-git-repos-against-accidental-rm.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>Everyone who has spent a sizable portion of their life in terminals has experienced that "oh shit" moment: you realize what you've done immediately after you've hit enter, but it's already too late. And needlessly to say, many of those are associated to accidental <code>rm</code>s.</p>
<p>I just had one of those moments. I was going to delete a subdirectory of <code>~/.config</code>, but hit return prematurely, and the command line ended up being <code>rm -r ~/.config</code>. Imagine the horror one second later. Fortunately I was saved by the read-only objects in <code>.git</code>, which triggered prompts; however, damage was already done, to some extent. I had to reinit the repo and do a hard reset, and a corrupted submodule was in my way (it blocked my attempt of <code>git reset --hard</code>) which I eventually had to completely remove and re-add. In the end everything was recovered (hopefully) and back to normal, but this episode was definitely not great for heart health, which led me to rethink <code>rm</code>.</p>
<p>I've tried several safer <code>rm</code> solutions before. The first and obvious is to alias <code>rm</code> to <code>rm -i</code>, but having to answer dozens of prompts a day (or more) is agonizing and unproductive. I've also tried trashing, but a nonempty trash can makes me sick, so not for me either. I also used <code>safe-rm</code> for a couple of months, but without supplying my own blacklist (I have none to be blacklisted), I've never hit the default blacklist; apparently I'm not stupid enough to mess in system locations, so this won't really help much. Fortunately though, this time I might have found a very good solution for myself.</p>
<p>The idea is to protect all git repos. Git repos<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-12-08-safeguarding-git-repos-against-accidental-rm.html#fn1" id="fnref1"><sup>1</sup></a> are among the most valuable assets of programmers, and they have the nice property of not being completely removable without <code>-f</code> or <code>--force</code> (the work tree of a submodule, where <code>.git</code> is a regular file containing the relative path of the git dir, can be removed without <code>--force</code>, but we don't want to damage submodules anyway, so let's not single them out). It's unlikely that we would intend to remove a repo directory without specifying <code>-f</code> or <code>--force</code>, so let's just reject all such <code>rm</code> calls.</p>
<p>The wrapper is very easy to write. Here's one implementation for Zsh with support for both GNU coreutils and BSD <code>rm</code>.</p>
<div class="sourceCode"><pre class="sourceCode zsh"><code class="sourceCode zsh"><span class="kw">rm</span> <span class="kw">()</span> <span class="kw">{</span>
<span class="kw">setopt</span> localoptions noshwordsplit noksharrays
<span class="kw">local</span> <span class="ot">args_backup</span> <span class="ot">force</span> <span class="ot">node</span>
<span class="kw">set</span> -A args_backup <span class="ot">$@</span>
<span class="kw">while</span> <span class="kw">:</span>; <span class="kw">do</span>
<span class="kw">case</span> <span class="ot">$1</span><span class="kw"> in</span>
--force<span class="kw">|</span>-*f*<span class="kw">)</span> <span class="ot">force=</span>1 <span class="kw">&&</span> <span class="kw">shift;;</span>
--<span class="kw">)</span> <span class="kw">shift</span> <span class="kw">&&</span> <span class="kw">break;;</span>
-*<span class="kw">)</span> <span class="kw">shift;;</span>
*<span class="kw">)</span> <span class="kw">break;;</span>
<span class="kw">esac</span>
<span class="kw">done</span>
<span class="kw">for</span> node; <span class="kw">do</span>
<span class="co"># -f, --force hasn't been specified && node is a git repo</span>
[[ -z <span class="ot">$force</span> <span class="kw">&&</span> -e <span class="ot">$node</span>/.git ]] <span class="kw">&&</span> <span class="kw">{</span>
<span class="kw">printf</span> <span class="st">"\e[31m'%s' is a git repo -- won't remove without the -f or --force option\e[0m\n"</span> <span class="ot">$node</span>
<span class="kw">return</span> 1
<span class="kw">}</span>
<span class="kw">done</span>
<span class="kw">command</span> <span class="kw">rm</span> <span class="ot">$args_backup</span>
<span class="kw">}</span></code></pre></div>
<p>Personally, I stick it into a <a href="https://github.com/zmwangx/prezto/tree/master/modules/rm_guard">Prezto module</a> available from my fork. Hopefully it will serve me well this time round.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>In this article, "repo" stands for the work tree of a repo, unless otherwise noted; the actual repo with git objects is referred to as "git dir".<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-12-08-safeguarding-git-repos-against-accidental-rm.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">Bash function exporting fiasco</title><link href="http://archive.zhimingwang.org/blog/2015-11-25-bash-function-exporting-fiasco.html"/><updated>2015-11-25T15:38:13-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-11-25-bash-function-exporting-fiasco.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>Bash is the only major shell (and the only shell that I know of) that implements function exporting. By now everyone should have heard of this feature, I suppose, after the publicity of Shellshock last year. I was personally introduced to it while writing parallel processing scripts <a href="https://www.gnu.org/software/parallel/parallel_tutorial.html#Transfer-environment-variables-and-functions">with GNU Parallel</a> (long before Shellshock), and it seemed useful and clever at that time. Back then I often wondered why it didn't make its way into Z shell. However, now that I'm much more seasoned in shell scripting, I can see why and how this feature is troubled and of debatable value.</p>
<p>Two problems lie at the heart of function exporting:</p>
<ol style="list-style-type: decimal">
<li>As always, everything clever comes at a cost;</li>
<li>Code execution from untrusted source.</li>
</ol>
<p>Regarding the first problem, the cost of function exporting is to mess with the environment, in a very hackish way. The environment was designed to hold data, not code, and we're not in the utopia of Lisp; but bash forced its way through. Pre-shellshock, exported <code>func</code> was stored as <code>func=() {...</code> in env; post-shellshock, it was first <code>BASH_FUNC_func()=() {...</code> (which didn't entirely fix the issue), and then <code>BASH_FUNC_func%%=() {...</code>.</p>
<p>The second problem doesn't need much explanation — <em>shellshock it was</em>. It has been extensively documented elsewhere, so I'll just succinctly comment that to load exported functions into a subshell, function definitions have to be retrieved from the environment and executed (again because we're not in the utopia of Lisp<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-11-25-bash-function-exporting-fiasco.html#fn1" id="fnref1"><sup>1</sup></a>), and loading is done passively from the subshell user's point of view, hence the code execution bug(s). The bug(s) has(have) allegedly been fixed, but code execution (presumably with the appropriate safeguards now) still can't be avoided altogether, so just like a sanitized <code>eval</code>, it would still wake you up at night.</p>
<p>Well, if that's all I have to say, I wouldn't have started this post today. The thing that's bugging me is another issue I've found recently that's entirely avoidable, yet upon which we'll probably never see light ever after due to a combination of factors.</p>
<p>It started with <a href="http://stackoverflow.com/q/33819243/19447840">this question on SO</a>. While troubleshooting I quickly noticed that a Bash-emulated <code>sh</code> imports those <code>BASH_FUNC</code>s from the environment:</p>
<pre><code>> bash -c 'func () { echo "exported function loaded"; } && export -f func && ln -sf /bin/bash sh && ./sh -c func'
exported function loaded</code></pre>
<p>It gets worse when the function isn't Bourne shell compatible (e.g., when it uses process substitution):</p>
<pre><code>> bash -c 'func () { cat <(echo hello); } && export -f func && ln -sf /bin/bash sh && ./sh -c func'
cat: <(echo hello): No such file or directory</code></pre>
<p>That's surprising but not scary enough, because if you're not a fool you won't call <code>func</code> in <code>sh</code> anyway. However, if you're unfortunate enough to be dealing with <code>/bin/sh</code> on OS X (bash 3.2 under the hood, modified by Apple or not I'm not sure), then all hell break loose:</p>
<pre><code>> bash -c 'func () { cat <(echo hello); } && export -f func && /bin/sh -c :' # OS X only
/bin/sh: func: line 0: syntax error near unexpected token `('
/bin/sh: func: line 0: `func () { cat <(echo hello)'
/bin/sh: error importing function definition for `func'</code></pre>
<p>Note that we're actively doing nothing in <code>sh</code>, yet we get all these syntax errors from loading <code>func</code>. This happens to every invocation of <code>sh</code>, and as you might expect, there are no shortage of programs that are either <code>sh</code> scripts (e.g., fasd) or have internal <code>sh</code> calls (e.g., GNU Parallel<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-11-25-bash-function-exporting-fiasco.html#fn2" id="fnref2"><sup>2</sup></a>). A single export of a Bourn shell incompatible function will haunt you through the entire session. Oops.</p>
<p>As I said, I don't know if the displayed error messages are due to Apple's modifications (anyone willing to look at the <a href="https://opensource.apple.com/source/bash/bash-97/">source code</a>?), since a symlink named <code>sh</code> to <code>/bin/bash</code> doesn't print error messages, but instead load the wrong function, which is almost as bad but less annoying to innocent users. At any rate, it's not even worth reporting, either to GNU or Apple, because we're stuck with bash 3.2 for <code>/bin/sh</code> forever (thank you GPLv3), and it takes a hell of a vulnerability like shellshock to get a small update out of Apple's hands. We can install newer shells to <code>/usr/local</code> as much as we'd like to, but <code>/bin/sh</code> is simply the final word for many tasks involving the shell. Yet it's stained by this troubled bash-specific feature, and it's not going anywhere. So sad.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>I'm not commenting on the security of Lisp.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-11-25-bash-function-exporting-fiasco.html#fnref1">↩︎</a></p></li>
<li id="fn2"><p><strong>04/14/2015 Update.</strong> GNU Parallel is no longer haunted by this issue since <a href="http://git.savannah.gnu.org/cgit/parallel.git/commit/?id=3d919c6cd427e9615f56f260eb959a44d5d32c18"><code>3d919c6</code></a>.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-11-25-bash-function-exporting-fiasco.html#fnref2">↩︎</a></p></li>
</ol>
</div>
]]></content></entry><entry><title type="html">We need a programming keyboard on iOS</title><link href="http://archive.zhimingwang.org/blog/2015-11-15-we-need-a-programming-keyboard-on-ios.html"/><updated>2015-11-15T02:17:05-08:00</updated><id>http://archive.zhimingwang.org/blog/2015-11-15-we-need-a-programming-keyboard-on-ios.html</id><author><name>Zhiming Wang</name><uri>http://archive.zhimingwang.org/</uri><email>zmwangx@gmail.com</email></author><content type="html"><![CDATA[
<p>We do. If you ever tried to say something on GitHub (web) or StackOverflow (web or app) on iOS, you'll probably agree with me. The stock keyboard (or any third party keyboard that I've heard of) is simply awful at this. Typing on iOS software keyboard is unpleasant enough to begin with, but behold:</p>
<ul>
<li>Auto"correct" messes up everything as fast as you can type, which isn't really fast anyway; might as well call it autorot.</li>
<li>The backtick is a click plus a loooong click (on the single quote key) plus another click away. Good luck typing code in Markdown,<a class="footnoteRef" href="http://archive.zhimingwang.org/blog/2015-11-15-we-need-a-programming-keyboard-on-ios.html#fn1" id="fnref1"><sup>1</sup></a> especially if you use GFM fenced code block like all of us do.</li>
<li>Brackets, curly braces, the underscore, the pound, etc. are all three clicks away.</li>
</ul>
<p>The solution is pretty obvious actually. I don't know about smaller phones, but the software keyboard on a landscape iPhone 6 Plus has four rows, which takes up about 40% of vertical screen estate, and it has fourteen keys in the top row. With a little bit of effort it can be made into a five-row, full-sized keyboard (without arrow keys perhaps) without taking up a ridiculous amount of space. Since the horizontal 6 Plus could handle it, any iPad should be able to handle it too; definitely shouldn't be an iPad Pro-only luxury. Turn off autocorrect on top of that, and you get a decent programming (or better put, programmer-oriented) keyboard.</p>
<p>This is merely a rant, but it would awesome if anyone sets out to make one.</p>
<div class="footnotes">
<hr/>
<ol>
<li id="fn1"><p>To be fair, typing BBCode is even worse. Unfortunately that's what Ars Technica use, and I've given up on commenting there.<a class="footnotes-backlink" href="http://archive.zhimingwang.org/blog/2015-11-15-we-need-a-programming-keyboard-on-ios.html#fnref1">↩︎</a></p></li>
</ol>
</div>
]]></content></entry></feed>
|