feedparser 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,173 @@
1
+ {
2
+ "version": "https://jsonfeed.org/version/1",
3
+ "title": "inessential.com",
4
+ "description": "Brent Simmons’s weblog.",
5
+ "home_page_url": "http://inessential.com/",
6
+ "feed_url": "http://inessential.com/feed.json",
7
+ "user_comment": "This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL — http://inessential.com/feed.json — and add it your reader.",
8
+ "favicon": "http://inessential.com/favicon.ico",
9
+ "author": {
10
+ "name": "Brent Simmons",
11
+ "url": "http://inessential.com/",
12
+ "avatar": "http://ranchero.com/downloads/brent_avatar.png"
13
+ },
14
+ "items": [
15
+ {
16
+ "id": "http://inessential.com/2017/05/17/json_feed",
17
+ "url": "http://inessential.com/2017/05/17/json_feed",
18
+ "title": "JSON Feed",
19
+ "content_html": "<p>I was hesitant, even up to this morning, to publish the <a href=\"https://jsonfeed.org/version/1\">JSON Feed spec</a>.</p>\n\n<p>If you read Dave Winer’s <a href=\"http://scripting.com/2017/05/09/rulesForStandardsmakers.html\">Rules for standards-makers</a>, you’ll see that we did a decent job with some of the rules — the spec is written in plain English, for example — but a strict application of the rules would have meant not publishing at all, since “Fewer formats is better.”</p>\n\n<p>I agree completely — but I also believe that developers (particularly Mac and iOS developers, the group I know best) are so loath to work with XML that they won’t even consider building software that needs an XML parser. Which says to me that JSON Feed is needed for the survival of syndication.</p>\n\n<p>I could be wrong, of course. I admit.</p>\n\n<h4>Feed Reader Starter Kit</h4>\n\n<p>See my <a href=\"https://github.com/brentsimmons/RSXML\">RSXML repository</a> for Objective-C code that reads RSS, Atom, and OPML. I’ve done the work for you of supporting those formats. Go write a feed reader! Seriously. Do it.</p>\n\n<p>I planned to have a JSON Feed parser for Swift done for today, but other things got in the way. It’s coming soon. But you probably don’t actually need any sample code, since JSON is so easy to handle.</p>\n\n<h4>Feedback so far</h4>\n\n<p>Feedback has been interesting so far. Some <a href=\"https://github.com/brentsimmons/JSONFeed\">questions</a> on the GitHub repo need answering.</p>\n\n<p>Some people have said this should have happened ten years ago, and other people have said that they hate how developers jump on the latest fad (JSON).</p>\n\n<p>And some people really like the icon:</p>\n\n<p><img src=\"http://jsonfeed.org/graphics/icon.png\" height=70 width=70 /></p>\n\n<h4>Microformats</h4>\n\n<p>One of the more serious criticisms was this: why not just support the <a href=\"http://microformats.org/wiki/hatom\">hAtom microformat</a> instead? Why do another side-file?</p>\n\n<p>My thinking:</p>\n\n<p>My experience as a feed reader author tells me that people screw up XML, badly, all the time — and they do even less well with HTML. So embedding info in HTML is just plain too difficult. In practice it would be even buggier than XML-based feeds.</p>\n\n<p>And there are other advantages to decoupling: a side-file can have 100 entries where there are only 10 on an HTML page, for instance. A side-file can have extra information that you wouldn’t put on an HTML page. And yet, despite the extra information, a side-file can be much smaller than an HTML page, and it can often be easier to cache (since it’s not different based on a logged-in user, for instance).</p>\n\n<p>Microformats sounds elegant, but I don’t prize elegance as much as I value things that work well.</p>",
20
+ "date_published": "2017-05-17T13:22:14-07:00"
21
+ },
22
+ {
23
+ "id": "http://inessential.com/2017/05/01/frontier_diary_8_when_worlds_collide",
24
+ "url": "http://inessential.com/2017/05/01/frontier_diary_8_when_worlds_collide",
25
+ "title": "Frontier Diary #8: When Worlds Collide",
26
+ "content_html": "<p>I spent the weekend making a bunch of progress on the compiler. It has two pieces: a <a href=\"https://github.com/brentsimmons/Frontier/blob/master/UserTalk/UserTalk/Compiler/Tokenizer.swift\">tokenizer</a>, which I created by rewriting the original C code (<a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierOrigFork/Common/source/langscan.c\">langscan.c</a>) in Swift, and a parser.</p>\n\n<p>The parser in OrigFrontier was generated by MacYacc, which is similar to Yacc, which is similar to <a href=\"https://www.gnu.org/software/bison/\">Bison</a>, which is on my Mac. The thing about the parser is that it’s C code, and the rest of the app is Swift.</p>\n\n<p>How do you bridge the two worlds? Easy answer: with Objective-C, which is a superset of C and which plays nicely (enough) with Swift.</p>\n\n<p>So I renamed langparser.y — the rules file that the parser generator uses — to <a href=\"https://github.com/brentsimmons/Frontier/blob/master/UserTalk/UserTalk/Compiler/langparser.ym\">langparser.ym</a> so that Xcode would know to treat the generated parser source as Objective-C. I edited it slightly, not to change the grammar rules but to change how nodes are created (as return values rather than via inout).</p>\n\n<p>I also made my <a href=\"https://github.com/brentsimmons/Frontier/blob/master/UserTalk/UserTalk/CodeTreeNode.swift\">CodeTreeNode</a> class, written in Swift, an Objective-C class so that it would be visible to my Objective-C code.</p>\n\n<p>And then, finally, I started a build…</p>\n\n<p>…and then it stopped with an error because the parser places my <code>CodeTreeNode</code> in a C union, which isn’t allowed in ARC.</p>\n\n<p>Crushed.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>I think I have three options:</p>\n\n<ol>\n<li>Go down the rabbit hole of figuring out how to get the parser to work with ARC.</li>\n<li>Go with the flow: have the parser generate nodes that are, as in OrigFrontier, C structs. The last compilation step would be Objective-C code that translates that tree of C structs into a tree of <code>CodeTreeNode</code> objects, and then disposes the C-struct-node-tree.</li>\n<li>Write the parser by hand, in Swift.</li>\n</ol>\n\n\n<p>My thinking:</p>\n\n<p>I could waste a ton of time on #1, and bending tools in that way can be pretty frustrating work when they refuse to bend.</p>\n\n<p>With #2 I’d feel a bit weird about the redundancy: building a tree and then building a copy of that tree with a different type of object.</p>\n\n<p>My heart tells me #3 is the answer. After all, I’ve already done the tokenizer. How hard would it be to parse those tokens into a code tree? I could skip C and Objective-C altogether and stay in Swift. And it would be <em>so fun</em>. (Because that’s precisely the style of weirdo I am.)</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>But the real answer is #2. Writing a parser by hand would take way longer than I think. Given enough tests, it shouldn’t be a huge source of bugs, but still.</p>\n\n<p>The thing about #2 is that yes, it’s redundant, it’s doing more work than it needs to, ideally — but my bet is that it would still be so fast that you wouldn’t be able to tell the difference. Computers are so good at this kind of thing. It’s not like reading files or networking; it’s just in-memory traversal and creating/releasing things.</p>\n\n<p>You remember in Indiana Jones that guy with the twirling swords, and Indy gives that look and then just shoots him? The second option is the Indiana Jones solution.</p>\n\n<p><i>Update 2:05 pm</i>: Two people have already written me to recommend <a href=\"http://www.antlr.org\">ANTLR</a>. So I will definitely give that a look. It might be exactly what I need.</p>",
27
+ "date_published": "2017-05-01T13:34:23-07:00"
28
+ },
29
+ {
30
+ "id": "http://inessential.com/2017/04/27/frontier_diary_7_pretty_much_everythin",
31
+ "url": "http://inessential.com/2017/04/27/frontier_diary_7_pretty_much_everythin",
32
+ "title": "Frontier Diary #7: Pretty Much Everything Throws",
33
+ "content_html": "<p>A script can throw an error, either intentionally (via the <code>scriptError</code> verb) or by doing something, such as referencing an undefined object, that generates an error.</p>\n\n<p>OrigFrontier was written in C, which has no error-throwing mechanism, and so it worked like this: most runtime functions returned a boolean (for success or failure), and the return value was passed in by reference. If there was an error, the function would set a global error variable and return false. The caller would then have to check that global to see if there was an error, and then do the right thing.</p>\n\n<p>This was not unreasonable, given the language and the times (early ’90s) and also given the need to be very careful about unwinding memory allocations.</p>\n\n<p>But, these days, it seems to me that Swift’s error system is the way to go. There’s just one downside to that, and it’s that I have to do that do/try/catch dance all over the place, since pretty much any runtime function can throw an error.</p>\n\n<p>Even the coercions can throw, so last night I changed the <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierData/FrontierData/Value/ValueProtocol.swift\">Value</a> protocol so that <code>asInt</code> and so on are now functions, since properties can’t throw (at least not yet).</p>\n\n<p>The extra housekeeping — the do/try/catch stuff — kind of bugs me, but it’s honest. I considered making script errors just another type of Value — but that meant that all those callers have to check the returned Value to see if it’s an error, and then do the right thing. Better to just use Swift’s error system, because it makes for more consistent code, and it makes sure I’m catching errors in every case.</p>\n\n<p>It also means I’m not multiplying entities. A Swift error is a script error, and vice versa.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>Working on this code is like applying the last 25 years of programming history all at once.</p>\n\n<p>A completely different type of error is a <em>bug</em>, and I’m certain to write a bunch of them, because that’s how programming goes.</p>\n\n<p>That’s where unit tests come in. Frontier has long had a stress-test suite of scripts — you’d launch the app, run that suite, wait a while, and see if there are any errors. This was critically helpful.</p>\n\n<p>But OrigFontier didn’t have unit tests at the C code level. The new version does. (Well, <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierVerbs/FrontierVerbsTests/Math.swift\">I’ve started them anyway</a>.) This means I can more easily follow <a href=\"http://scripting.com/2002/09/29.html#rule1\">Rule 1</a> — the no-breakage rule — and can also more easily follow Rule 1b — the don’t-break-Dave rule.</p>\n\n<p>PS I’ve added a <a href=\"http://inessential.com/frontierdiary\">collection page for the Frontier Diary</a>, as I did with earlier diaries. There’s a link to it in the footer of every page on the blog.</p>",
34
+ "date_published": "2017-04-27T13:30:42-07:00"
35
+ },
36
+ {
37
+ "id": "http://inessential.com/2017/04/26/frontier_diary_6_ballard_from_the_par",
38
+ "url": "http://inessential.com/2017/04/26/frontier_diary_6_ballard_from_the_par",
39
+ "title": "Frontier Diary #6: Ballard, from the Parallel Universe",
40
+ "content_html": "<p>In another universe I didn’t decide to port Frontier — instead, I started over from scratch on an app <em>inspired</em> by Frontier.</p>\n\n<p>In that universe, the new scripting language, descended from UserTalk, is called Ballard. <a href=\"http://inessential.com/ballard_lang\">And it’s documented</a>.</p>",
41
+ "date_published": "2017-04-26T13:04:04-07:00"
42
+ },
43
+ {
44
+ "id": "http://inessential.com/2017/04/25/my_microblog",
45
+ "url": "http://inessential.com/2017/04/25/my_microblog",
46
+ "title": "My Microblog",
47
+ "content_html": "<p>I’m on Manton‘s cool new microblogs system. Here’s where you can follow me, once you’re on the system: <a href=\"http://micro.blog/brentsimmons\">http://micro.blog/brentsimmons</a>.</p>\n\n<p>And here’s my microblog: <a href=\"http://brent.micro.blog/\">http://brent.micro.blog/</a>. (Which you can read using RSS, whether you’re on the system or not.)</p>\n\n<p>I wrote about three-quarters of my own single-user microblog system — and then stopped because I didn’t feel like running a server and because Manton’s service is so good.</p>",
48
+ "date_published": "2017-04-25T14:27:28-07:00"
49
+ },
50
+ {
51
+ "id": "http://inessential.com/2017/04/25/frontier_diary_5_values_and_progress_o",
52
+ "url": "http://inessential.com/2017/04/25/frontier_diary_5_values_and_progress_o",
53
+ "title": "Frontier Diary #5: Values and Progress on the Language",
54
+ "content_html": "<p>I put the <a href=\"https://github.com/brentsimmons/Frontier\">Frontier repository</a> up on GitHub.</p>\n\n<p>(The build is currently broken. This is bad discipline, but since it’s still just me, I forgive myself. Sometimes I run out of time and I just commit what I have.)</p>\n\n<p>The repo has my new code and it also contains <a href=\"https://github.com/brentsimmons/Frontier/tree/master/FrontierOrigFork\">FrontierOrigFork</a>, which is the original Frontier source with a bunch of deletions and some changes. The point is to give me 1) code to read and 2) a project that builds and runs on <a href=\"http://inessential.com/2017/04/03/frontier_diary_1_vm_life\">my 10.6.8 virtual machine</a>.</p>\n\n<p>The original code is in C, and the port is, at least so far, all in Swift. In the end it should be <em>almost</em> all in Swift, but I anticipate a couple places where I may need to use Objective-C.</p>\n\n<p>Here’s one of the Swift wins:</p>\n\n<h4>Values</h4>\n\n<p>Since Frontier contains a database and scripting language, there’s a need for some kind of value object that could be a boolean, integer, string, date, and so on.</p>\n\n<p>Original Frontier used a <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierOrigFork/Common/headers/lang.h\">tyvaluedata</a> union, with fields for the various types of values.</p>\n\n<p>This is a perfectly reasonably approach in C. It’s great because you can pass the same type of value object everywhere.</p>\n\n<p>Were I writing this in Objective-C, however, I’d create a <code>Value</code> protocol, and then create new value objects for some types and also extend existing objects (<code>NSNumber</code>, <code>NSString</code>, etc.) to conform to the <code>Value</code> protocol. This would still give me the upside — passing a <code>Value</code> type everywhere — while reducing the amount of boxing.</p>\n\n<p>But: this still means I have an <code>NSNumber</code> when I really want a BOOL. Luckily, in Swift I can go one better: I can extend types such as <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierData/FrontierData/Value/ValueBool.swift\">Bool</a> and <code>Int</code> to conform to a <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierData/FrontierData/Value/ValueProtocol.swift\">Value</a> protocol.</p>\n\n<p>This means passing around an <em>actual</em> <code>Bool</code> rather than a boxed boolean. I like this a ton. It feels totally right.</p>\n\n<p>Other topic:</p>\n\n<h4>Language Progress</h4>\n\n<p>I’m still in architectural mode, where I’m writing just enough code to validate and refine my decisions. A couple days ago I started on the <a href=\"https://github.com/brentsimmons/Frontier/blob/master/UserTalk/UserTalk/LangEvaluator.swift\">language evaluator</a> — the thing that actually runs scripts.</p>\n\n<p>It works as you expect: it takes a compiled code tree and recursively evaluates it. It’s not difficult — it’s just that it’s going to end up being a fair amount of code.</p>\n\n<p>I’ve done just enough to know that I’m on the right path. (The Swift code looks a lot like the C code in OrigFrontier’s <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierOrigFork/Common/source/langevaluate.c\">langevaluate.c</a>. See <code>evaluateList</code>, for instance.)</p>\n\n<p>The next step is for me to build the parser. I thought about writing a parser by hand, because it sounds like fun, and it would give me some extra control — but, really, it would slow me way down, so forget it.</p>\n\n<p>OrigFrontier generated its parser by passing a grammar file — <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierOrigFork/Common/source/langparser.y\">langparser.y</a> — to MacYacc (there was such a thing!), which generated <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierOrigFork/Common/source/langparser.c\">langparser.c</a>.</p>\n\n<p>I’ll do a similar thing, except using <a href=\"https://www.gnu.org/software/bison/\">Bison</a> (which is compatible with Yacc). Or, possibly, using the <a href=\"http://www.hwaci.com/sw/lemon/\">Lemon parser generator</a> instead. Either way, I’ll want the generated code to be Objective-C. (Well, mostly C, but with Objective-C objects instead of structs.) (I don’t know of a generator that would create Swift code.)</p>\n\n<p>This is completely new territory for me, and is exciting.</p>\n\n<p>(Almost forgot to mention: I’ll need to write a tokenizer. This means porting <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierOrigFork/Common/source/langscan.c\">langscan.c</a>. I’ll need to do this first, since the parser generator needs it. So this is the real next step.)</p>",
55
+ "date_published": "2017-04-25T13:26:33-07:00"
56
+ },
57
+ {
58
+ "id": "http://inessential.com/2017/04/14/save_300_on_coccoaconf_next_door",
59
+ "url": "http://inessential.com/2017/04/14/save_300_on_coccoaconf_next_door",
60
+ "title": "Save $300 on CocoaConf Next Door",
61
+ "content_html": "<p>My pals at CocoaConf asked me to remind you that the <a href=\"https://twitter.com/cocoaconf/status/852898192035282944\">Early Bird sale ends in two weeks</a> for CocoaConf Next Door — the one taking place in San Jose during WWDC.</p>\n\n<p>I’ll be there. At least in the afternoons.</p>\n\n<p>Check out the <a href=\"http://cocoaconf.com/nextdoor/speakers\">speakers list</a>. Yummy, chewy, nutty speakers list.</p>",
62
+ "date_published": "2017-04-14T13:53:02-07:00"
63
+ },
64
+ {
65
+ "id": "http://inessential.com/2017/04/14/frontier_diary_4_the_quickdraw_problem",
66
+ "url": "http://inessential.com/2017/04/14/frontier_diary_4_the_quickdraw_problem",
67
+ "title": "Frontier Diary #4: The QuickDraw Problem and Where It Led Me",
68
+ "content_html": "<p>In my fork of Frontier there are still over 600 deprecation warnings. A whole bunch of these are due to <a href=\"https://en.wikipedia.org/wiki/QuickDraw\">QuickDraw</a> calls.</p>\n\n<p>For those who don’t know: QuickDraw was how, in the old days, you drew things to the Mac’s screen. It was amazing for its time and pretty easy to work with. Functions included things like <code>MoveTo</code>, <code>LineTo</code>, <code>DrawLine</code>, <code>FrameOval</code>, and so on. All pretty straightforward.</p>\n\n<p>These days we have Core Graphics instead, and we have higher-level things like <code>NSBezierPath</code>. QuickDraw was simpler — though yes, sure, that was partly because it did less.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>I was looking at all these deprecation warnings for QuickDraw functions and wondering how I’m ever going to get through them.</p>\n\n<p>I could, after all, convert all or most of them to the equivalent Core Graphics thing. But sheesh, what a bunch of work.</p>\n\n<p>And, in the end, it would still be a Carbon app, but with modern drawing.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>So I thought about it from another angle. The goal is to get to the point where it’s a 64-bit Cocoa app. All these QuickDraw calls are in the service of UI — so why not just start over with a Cocoa UI?</p>\n\n<p>The app has some outlines (database browser, script editor, etc.), a basic text editor, and a handful of small dialogs. <em>And all of that is super-easy in Cocoa.</em></p>\n\n<p>Use an <code>NSOutlineView</code>, <code>NSTextView</code>, and some xibs for the dialogs, and we’re done. (Well, after <em>some</em> work, but not nearly the same amount of work as actually writing an outliner from scratch.)</p>\n\n<p>In other words, instead of going from the bottom up — porting the existing source code — I decided to start from the top down.</p>\n\n<p>I started a new workspace and started a new Frontier project: a Cocoa app with Swift as the default language.</p>\n\n<p>Then I looked at the existing source and thought about how to organize things. I came up with this:</p>\n\n<ul>\n<li>Frontier — App UI</li>\n<li>UserTalk.framework — the language</li>\n<li>FrontierVerbs.framework - the standard library</li>\n<li>FrontierDB.framework — the object database</li>\n<li>FrontierCore.framework — common utility functions and extensions</li>\n</ul>\n\n\n<p>I like using frameworks, because it helps enforce separation, and it helps in doing unit testing. And frameworks are so easy with Swift these days.</p>\n\n<p>Hardly any of this is filled-in yet. I’ve got the barest start on FrontierVerbs. <a href=\"https://twitter.com/tedchoward\">Ted Howard</a>, my partner in all this, is taking UserTalk.framework and FrontierDB.framework.</p>\n\n<p>In the end, it’s possible that no code from the original code base survives. Which is totally fine. But it also means that this is no quick project.</p>\n\n<p>At this point I should probably put it up on GitHub, since it’s easier to write about it if I can link to the code. I’ll do that soon, possibly on the weekend.</p>",
69
+ "date_published": "2017-04-14T13:14:20-07:00"
70
+ },
71
+ {
72
+ "id": "http://inessential.com/2017/04/13/frontier_diary_3_built-in_verbs_config",
73
+ "url": "http://inessential.com/2017/04/13/frontier_diary_3_built-in_verbs_config",
74
+ "title": "Frontier Diary #3: Built-in Verbs Configuration",
75
+ "content_html": "<p>Frontier’s standard library is known as its built-in verbs. There are a number of different tables: <code>file</code>, <code>clock</code>, <code>xml</code>, and so on. Each contains a number of verbs: <code>file.readWholeFile</code>, <code>clock.now</code>, and so on.</p>\n\n<p>Most of these verbs are implemented in C, in the kernel, rather than as scripts. At the moment, to add one of these kernel verbs, you have to jump through a few hoops: edit a resource, add an integer ID, add to a switch statement, etc. It’s a pain and is error-prone.</p>\n\n<p>So I want to re-do this in Swift, because I’m all about Swift. And I want adding verbs to be fool-proof: I don’t want to remember how to configure this every single time I add a verb. Adding a verb needs to be <em>easy</em>.</p>\n\n<p>My thinking:</p>\n\n<ul>\n<li>Give each table its own class: ClockVerbs, FileVerbs, etc.</li>\n<li>Have each class report the names of the verbs it supports. These need to be strings, because we get a string at runtime.</li>\n<li>Run a verb simply by looking up the selector, performing it, and returning the result.</li>\n</ul>\n\n\n<p>To make things easy and obvious, I think it should work like this: the selector for a given verb is its name plus a parameter. Then there’s not even a lookup step.</p>\n\n<p>Each verb will take a VerbParameters object and return a VerbResult object.</p>\n\n<pre><code>dynamic func readWholeFile(_ params: VerbParameters) -&gt; VerbResult\n</code></pre>\n\n<p>The flow goes like this:</p>\n\n<ol>\n<li>We have the string <code>file.readWholeFile</code>.</li>\n<li>We see the <code>file</code> suffix and so we know we need a <code>FileVerbs</code> object.</li>\n<li>We check <code>fileVerbs.supportedVerbs</code> (an array) to see if <code>readWholeFile</code> is in the list. It is.</li>\n<li>We construct a selector using the <code>readWholeFile</code> part of the string and we add a <code>:</code> character: <code>NSSelectorFromString(verbName + \":\")</code></li>\n</ol>\n\n\n<p>This is great! We’re almost home free. Then we run the verb:</p>\n\n<pre><code>if let result = perform(selector, with: params) as? VerbResult {\n return result\n}\n</code></pre>\n\n<p>That doesn’t work. We get:</p>\n\n<pre><code>Cast from 'Unmanaged&lt;AnyObject&gt;! to unrelated type 'VerbResult' always fails\n</code></pre>\n\n<p>Nuts.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>It was <em>so</em> close.</p>\n\n<p>In Objective-C this would have worked. And obviously, apparently, I still think in Objective-C.</p>\n\n<p>I investigated some other options. At one point enums were abused, because there’s <em>always</em>, in Swift, an enum-abuse step. But everything I tried was more code and was more error-prone, and my goal here is to improve the situation.</p>\n\n<p>I think, in the end, I’m going to do something that looks kind of ugly: a switch statement where the cases are string literals.</p>\n\n<pre><code>switch(verbName) {\ncase \"readWholeFile\":\n return readWholeFile(params)\n…\n}\n</code></pre>\n\n<p>“Nooooo!” you cry. I hear ya.</p>\n\n<p>My experience as an object-oriented programmer tells me this: if I write a <code>switch</code> statement, I blew it.</p>\n\n<p>And my experience as a programmer tells me that string literals are a bad idea.</p>\n\n<p>But the above may actually be the easiest to configure and maintain. Each string literal appears only in that one switch statement and nowhere else in the code. And the mapping between a verb name and its function couldn’t be more clear — it’s right there.</p>\n\n<p>(Yes, instead of using a string literal, I could create a String enum and switch on that. But that’s actually more code and more room for error. I’m going to have to type those string literals <em>somewhere</em>, so why not right where they’re used?)</p>\n\n<p>It does mean that <code>readWholeFile</code> appears three times in the code (the string literal, the call, and the function itself), and in an Objective-C version it would appear only twice (in a <code>supportedVerbs</code> array and the method itself).</p>\n\n<p>But. Well.</p>\n\n<p>I’m torn between shuddering in abject and complete horror at this solution and thinking, “Hey, that’s pretty straightforward. Anybody could read it. Anybody could edit it.” Which was the plan all along.</p>\n\n<p>And I get to stick with Swift, so there’s that.</p>\n\n<p>But, sure as shootin’, some day someone’s going to come across this code and say, “Brent, dude, are ya <em>new</em>?” And I’ll send them the link to this page.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p><i>Update the next day:</i> well, the <code>performSelector</code> thing <em>would</em> work, if only I’d known about Swift Unmanaged objects.</p>\n\n<p><a href=\"https://twitter.com/jckarter\">Joe Groff</a> told me how this works.</p>\n\n<p>Here’s the gist: the <code>Unmanaged&lt;AnyObject&gt;</code> just needs to be unwrapped by calling <code>takeRetainedValue</code> or <code>takeUnretainedValue</code>. Once unwrapped, it can be cast to <code>VerbResult</code>.</p>\n\n<p>All this means that I can use my original design, which is great news.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p><i>Update April 25, 2017:</i> I ended up using enums after all. See <a href=\"https://github.com/brentsimmons/Frontier/blob/master/FrontierVerbs/FrontierVerbs/VerbTables/MathVerbs.swift\">MathVerbs.swift</a> for an example.</p>",
76
+ "date_published": "2017-04-13T22:25:41-07:00"
77
+ },
78
+ {
79
+ "id": "http://inessential.com/2017/04/11/frontier_diary_2_two_good_ideas_that_a",
80
+ "url": "http://inessential.com/2017/04/11/frontier_diary_2_two_good_ideas_that_a",
81
+ "title": "Frontier Diary #2: Two Good Ideas that Aren’t Good Anymore",
82
+ "content_html": "<p>Strings in <a href=\"http://inessential.com/2017/04/03/frontier_diary_1_vm_life\">Frontier</a> are usually either Pascal strings or Handles.</p>\n\n<p>You probably don’t know what I’m talking about. I’ll explain.</p>\n\n<h4>Pascal Strings</h4>\n\n<p>Frontier is a Mac Toolbox app that’s been Carbonized just enough to run on OS X. You may recall that the Mac Toolbox was written so long ago that the <a href=\"https://developer.apple.com/legacy/library/documentation/mac/pdf/MacintoshToolboxEssentials.pdf\">original API</a> was in Pascal. That Pascal heritage lived on in many ways, even after everyone switched to C — and one of those ways was Pascal strings.</p>\n\n<p>A Pascal string is n bytes long, and the first byte specifies the length of the string, which leaves the rest of the bytes for the actual string. <code>Str255</code> was probably most common, and certainly is most common in Frontier, but there are also smaller sizes: <code>Str63</code> and <code>Str31</code>, for instance.</p>\n\n<p>Unlike C strings, they’re not zero-terminated, since there’s no need to calculate the length: you always know it from that first byte.</p>\n\n<p>You create a literal Pascal string like this…</p>\n\n<pre><code>Str255 s = \"\\pThis is a string\";\n</code></pre>\n\n<p>…and the compiler turns the <code>\\p</code> into the correct length (16 in this case).</p>\n\n<p>Now, I bet you’re saying to yourself, “Self, those Pascal strings are too small to be useful.”</p>\n\n<p>But consider this: every menu item name can fit into a Pascal string. You can fit a window title or a file name into a Pascal string (in fact, memory suggests that file names were even shorter, were <code>Str31</code> Pascal strings). Any label or message on any bit of UI is probably short enough to fit into a Pascal string. (Especially if you assume English.)</p>\n\n<p>So for GUI apps these were terrifically useful, and the 255-byte limit was no problem. (You can fit a tweet in a Pascal string, after all, with a bunch of room left over. [Well, depending on the size of the characters.])</p>\n\n<p>Frontier still uses them internally a ton. (For some reason, in the Frontier code, <code>Str255</code> strings are called <code>bigstring</code>, which sounds ironic, since they’re so small, but I think it was to differentiate them from even smaller Pascal strings such as <code>Str31</code>.)</p>\n\n<p>You might ask what the text encoding was for these strings.</p>\n\n<p>“Text whatzit?” I’d reply. “Oh, I see. Just regular.” (<a href=\"https://en.wikipedia.org/wiki/Mac_OS_Roman\">MacRoman</a>.)</p>\n\n<p>It was a good idea, but its time has come and gone. We have better strings these days.</p>\n\n<h4>Handles</h4>\n\n<p>Frontier includes a scripting language and a database, which means it certainly has a need for strings much larger than 255 bytes.</p>\n\n<p>It also needs heap storage for other things — binary data, structs, etc. — that could be much larger than 255 bytes.</p>\n\n<p>Enter the Handle. A Handle points to a pointer <em>that might move</em>: the memory you access via a Handle is <em>relocatable</em>.</p>\n\n<p>Which sounds awful, I know, but it was a smart optimization in the days when your Mac’s memory would be a single-digit number of megabytes, or even less than that.</p>\n\n<p>Here’s the problem: your application’s heap space can become fragmented. It could have a whole bunch of gaps in it after a while. So, to regain that memory, the system could compact the heap — it would remove those gaps, which means relocating the memory pointed to via a Handle.</p>\n\n<p>This is better than running out of memory, obviously. But it means that you have to be careful when dereferencing a Handle: you have to actually lock it first — <code>HLock(h)</code> — so that it can’t be moved while you’re using it. (And then you unlock it — <code>HUnlock(h)</code> — when finished.)</p>\n\n<p>Handles are also resizable — <code>SetHandleSize(h, size)</code> — and resizing a Handle can result in it needing to move, if there’s not enough space where it is. Or other Handles might move. You don’t ever know, and don’t care, and you think this is elegant because the system handles it all for you.</p>\n\n<p>All you have to deal with is an additional level of indirection (<code>**h</code> instead of <code>*p</code>), locking and unlocking it when needed, and disposing of it — <code>DisposeHandle(h)</code> — when finished. (No, there’s no reference counting, slacker.)</p>\n\n<p>Nowadays, on OS X, Handles don’t ever move and there’s no heap compaction. So there’s no reason for them whatsoever. And they are, as expected, deprecated.</p>\n\n<p>Nevertheless, Frontier, a Mac Toolbox app written in C, uses Handles everywhere.</p>\n\n<p>(I remember being shocked, when I first started learning Cocoa 15 years ago, that there were no Handles. It seemed <em>incredibly</em> daring that objects were just pointers. It made me nervous!)</p>\n\n<h4>The Size of the Job</h4>\n\n<p>Almost all the Mac APIs that Frontier uses are deprecated. That’s one thing.</p>\n\n<p>But it’s worse than just that: the ways Frontier handles strings and <em>pretty much every single thing it stores on the heap</em> are also deprecated.</p>\n\n<p>So: what to do?</p>\n\n<p>The end goal is a Cocoa app, which means I’ll be able to use Foundation, CoreFoundation, and Swift data types: NSString and Swift String, for instance. There are a number of different structs in the code, and those will be turned into Objective-C and Swift objects and Swift structs.</p>\n\n<p>The tricky part, though, is getting from here to there. I think the first step is to start with Objective-C and Foundation types and use them where possible. I can do that without actually turning it into a Cocoa app (the app will still have its own WaitNextEvent event loop and Carbon windows) — which means I’ll have to bracket all Objective-C code in autorelease pools, and I’ll have to use manual retains and releases. I’m not sure how far that will get me, but it will get me closer.</p>\n\n<p>PS Here are a couple articles by Gwynne Raskind on the Mac Toolbox you might enjoy: <a href=\"https://mikeash.com/pyblog/friday-qa-2012-01-13-the-mac-toolbox.html\">Friday Q&amp;A 2012-01-13: The Mac Toolbox</a> and <a href=\"https://mikeash.com/pyblog/the-mac-toolbox-followup.html\">The Mac Toolbox: Followup</a>.</p>",
83
+ "date_published": "2017-04-11T13:01:55-07:00"
84
+ },
85
+ {
86
+ "id": "http://inessential.com/2017/04/05/two_little-known_and_completely_unrelate",
87
+ "url": "http://inessential.com/2017/04/05/two_little-known_and_completely_unrelate",
88
+ "title": "Two Little-Known and Completely Unrelated Facts",
89
+ "content_html": "<p>One. <a href=\"https://www.omnigroup.com/omnioutliner\">OmniOutliner</a>’s outline view is implemented as CALayers rather than as a view with subviews. (I don’t think I’m giving away a trade secret here.)</p>\n\n<p>Two. If you eat fenugreek, your <a href=\"https://www.theatlantic.com/health/archive/2010/06/the-mystery-of-the-maple-syrup-smell/57980/\">armpits will smell like maple syrup</a>.</p>",
90
+ "date_published": "2017-04-05T16:57:59-07:00"
91
+ },
92
+ {
93
+ "id": "http://inessential.com/2017/04/05/ios_javascript_and_object_hierarchies",
94
+ "url": "http://inessential.com/2017/04/05/ios_javascript_and_object_hierarchies",
95
+ "title": "iOS, JavaScript, and Object Hierarchies",
96
+ "content_html": "<p><a href=\"http://iam.fahrni.me/2017/03/25/scripting-ios/\">Rob Fahrni</a>:</p>\n\n<blockquote><p>Given x-callback-url and App URL schemes in general it would be extremely cool to use those to create object hierarchies using JavaScript. Why JavaScript? Well, it’s native to iOS and applications can use the runtime.</p></blockquote>",
97
+ "date_published": "2017-04-05T14:53:01-07:00"
98
+ },
99
+ {
100
+ "id": "http://inessential.com/2017/04/05/cocoaconf_near_wwdc",
101
+ "url": "http://inessential.com/2017/04/05/cocoaconf_near_wwdc",
102
+ "title": "CocoaConf Near WWDC",
103
+ "content_html": "<p>There are a bunch of things happening near WWDC this year. Me, I’ll be at <a href=\"http://cocoaconf.com/blog/nextdoor\">CocoaConf Next Door</a>. I’m not preparing a talk, but I’ll probably be on a panel. And hanging out.</p>\n\n<p>Check out the <a href=\"http://cocoaconf.com/nextdoor/speakers\">speakers list</a>, which includes Omni’s own <a href=\"http://cocoaconf.com/nextdoor/speakers/162\">Liz Marley</a>. And a bunch of other people you totally want to see — Manton Reece, Jean MacDonald, Laura Savino, and plenty more.</p>\n\n<p>Also… <a href=\"http://altconf.com/\">AltConf</a> and <a href=\"https://layers.is/\">Layers</a> will be near WWDC. If you could be in three places at once, you would. Well, four, including WWDC itself, I suppose. :)</p>",
104
+ "date_published": "2017-04-05T14:35:05-07:00"
105
+ },
106
+ {
107
+ "id": "http://inessential.com/2017/04/05/omnioutliner_5_0_for_mac",
108
+ "url": "http://inessential.com/2017/04/05/omnioutliner_5_0_for_mac",
109
+ "title": "OmniOutliner 5.0 for Mac",
110
+ "content_html": "<p>I’ve been on the OmniOutliner team for over a year now. Though we don’t have positions like junior and senior developer, I enjoy calling myself the junior developer on the Outliner team, since I’m newest.</p>\n\n<p>I may be a new developer, but I’m not a new user — I’ve been using the app since the days when OmniOutliner 3 came installed on every Mac.</p>\n\n<p>Every time I start a talk, I outline it first. I organize the work I need to do in my side-project apps in OmniOutliner. And — don’t tell the OmniFocus guys, who are literally right here — sometimes I even use it for to-do management in general. I’d be lost without a great outliner.</p>\n\n<p>Anyway… <a href=\"https://www.omnigroup.com/blog/omnioutliner-5-is-now-available\">there’s a new version: OmniOutliner 5.0</a>. It’s my first dot-oh release at Omni, and I’m proud of it and proud of the team.</p>\n\n<p>As is common with our apps, we have two levels: a regular level and a Pro level. The regular level is called “Essentials” and is just $9.99. There’s a demo so you can try it out first.</p>\n\n<p>It syncs with iOS and with other Macs, by the way. Sync is free. And of course it comes with extensive documentation, and Omni’s awesome support humans are standing by.</p>\n\n<p><a href=\"https://www.omnigroup.com/omnioutliner/\">Get it while it’s hot</a>!</p>",
111
+ "date_published": "2017-04-05T10:44:45-07:00"
112
+ },
113
+ {
114
+ "id": "http://inessential.com/2017/04/03/frontier_diary_1_vm_life",
115
+ "url": "http://inessential.com/2017/04/03/frontier_diary_1_vm_life",
116
+ "title": "Frontier Diary #1: VM Life",
117
+ "content_html": "<p>It’s been years since I could build the <a href=\"http://frontierkernel.org\">Frontier kernel</a> — but I finally got it building.</p>\n\n<p>It’s really a ’90s Mac app that’s been Carbonized just enough to run on MacOS, but it’s by no means modern: it uses QuickDraw and early Carbon APIs. It’s written entirely in C.</p>\n\n<p>I got it building by installing MacOS 10.6.8 Server in VMWare. Installed Xcode 3.2.6. And now, finally, I can build and run it.</p>\n\n<h4>What is Frontier?</h4>\n\n<p>Frontier — as some of you know — was a UserLand Software product in the ’90s and 2000s. I worked there for about six years.</p>\n\n<p>The app is a development environment and runtime: a persistent, hierarchical database with a scripting language and a GUI for browsing and editing the database and for writing, debugging, and running scripts.</p>\n\n<p>The <a href=\"http://scripting.com/frontier/snippets/nerdsguide.html\">Nerd’s Guide to Frontier</a> gives some idea of what it’s like, though it was written before many of the later advances.</p>\n\n<p>Maybe you’ve never heard of it. But here’s the thing: it was in Frontier that the following were either invented or popularized and fleshed-out: scripted and templated websites, weblogs, hosted weblogs, web services over http, RSS, RSS readers, and OPML. (And things I’m forgetting.)</p>\n\n<p>Those innovations were due to the person — <a href=\"http://scripting.com/\">Dave Winer</a> — and to the times, the relatively early web days. But they were also in part due to the tool: Frontier was a fantastic tool for implementing and iterating quickly.</p>\n\n<h4>The Goal</h4>\n\n<p>The high-level goal is to make that tool available again, because I think we need it.</p>\n\n<p>The plan is to turn it into a modern Mac app, a 64-bit Cocoa app, and then add new features that make sense these days. (There are so many!) But that first step is a big one.</p>\n\n<p>The first part of the first step is simple, and it’s where I am now: mass deletions of code. Every reference to THINK_C and MPWC has to go. All references to the 68K and PPC versions must go. There was a Windows port, and all that code is getting tossed. And then I’ll see the scale of what needs to be done.</p>\n\n<p>(Note: my repo is a fork, and it’s not even on the web yet. The code I’m deleting is never really gone.)</p>\n\n<p>I’m doing a blog diary on it because it helps keep me focused. Otherwise I’m jumping around on my side projects. But if I have to write about it, then I’ll stay on target.</p>",
118
+ "date_published": "2017-04-03T13:44:34-07:00"
119
+ },
120
+ {
121
+ "id": "http://inessential.com/2017/03/31/the_goal",
122
+ "url": "http://inessential.com/2017/03/31/the_goal",
123
+ "title": "The Goal",
124
+ "content_html": "<p>The goal isn’t specifically impeachment and conviction. It’s for Trump to leave office.</p>\n\n<p>The stretch goal is that he dies broke and in prison.</p>\n\n<p>But we could settle for him going down in history as our worst President, as the worst person ever to become President, with the name Trump held in less esteem than that of Benedict Arnold, with Trumpism — that pseudo-populist white nationalism for the benefit of the super-rich — thoroughly loathed and seen for the brutish scam that it is.</p>\n\n<p>I think there comes a point before an actual trial in the Senate where Republican leaders — in Congress, in the Cabinet, wherever — realize that Trump can no longer govern, and they tell him so and urge him to resign.</p>\n\n<p>And I think he actually does resign at that point. He’s been through bankruptcy, and he’s shown that when there’s no path to winning, he’ll take the easiest route out of the situation, the route that leaves him the most status. He doesn’t have the stick-to-it-iveness to go to trial in the Senate: he’d quit.</p>\n\n<p>I don’t know what it will take to bring Republican leaders to this point. Their ongoing cowardice is the real scandal — when faced with a threat to our democracy, they play along because they’re hoping for some goodies.</p>\n\n<p>I don’t think they get to this point unless the public gets to this point, and so I look to the approval polls. If it gets below 30%, it’s probably there because of further revelations in the Russia affair, and it’s probably at the point where even cowards feel safe in doing the right thing — even if only to save their own necks, which will need saving.</p>\n\n<p>But right now Speaker Ryan won’t even replace Devin Nunes as chair of the house intelligence committee. So there’s still a long way to go.</p>",
125
+ "date_published": "2017-03-31T13:47:44-07:00"
126
+ },
127
+ {
128
+ "id": "http://inessential.com/2017/03/25/my_cocoaconf_yosemite_2017_talk",
129
+ "url": "http://inessential.com/2017/03/25/my_cocoaconf_yosemite_2017_talk",
130
+ "title": "My CocoaConf Yosemite 2017 Talk",
131
+ "content_html": "<p><a href=\"http://cocoaconf.com/yosemite\">Yosemite 2017</a> was so great. It always is.</p>\n\n<p>Below is the rough draft of my first-night talk. A few notes…</p>\n\n<p>The actual spoken version is probably not even close to the text, which was written before any rehearsal, and of course it’s never my intent to memorize it exactly.</p>\n\n<p>The bit with Laura Savino was a quick three-chord rock medley. We both played acoustic guitar and sang. It went like this:</p>\n\n<p>B: Louie Louie, oh baby, we gotta go<br />\nL: Yeah yeah yeah yeah yeah<br />\nB: Louie Louie, oh baby, we gotta go<br />\nL: Yeah yeah yeah yeah yeah<br />\nB: I live on an apartment on the 99th floor of my block<br />\nL: Hang on Sloopy, Sloopy hang on<br />\nB: I look out my window imagining the world has stopped</br />\nL: Hang on Sloopy, Sloopy hang on<br />\n[Slight change of chords]<br />\nB &amp; L: Teenage wasteland, oh yeah, only teenage wasteland [repeated]</p>\n\n<p>Here’s my <a href=\"https://www.youtube.com/watch?v=RzBz7p0A3-Y\">favorite video for Brimful of Asha</a>.</p>\n\n<p>During the Squirrel Picture interlude (slide #3) I told the <a href=\"http://inessential.com/2001/06/07/2001_06_07\">Squirrel Story</a>, which wasn’t planned or recently rehearsed, but I’ve told it often enough that it didn’t really need rehearsal.</p>\n\n<p>I dedicated the performance of Hallelujah to <a href=\"https://twitter.com/dori\">Dori Smith</a>.</p>\n\n<p>The talk was meant to be about 20 minutes long. Afterward I went around the room with a microphone and each person introduced themselves. (The talk’s job is to be a first-night ice-breaker talk.)</p>\n\n<p>I spent about 10 hours on rehearsal for those 20 minutes.</p>\n\n<p>Here’s the talk:</p>\n\n<h4>Slide #1: Three Chord Rock</h4>\n\n<p>Hi. I’m Brent.</p>\n\n<p>Before I get started — seeing my friend Brad Ellis reminded me of the most rock-n-roll moment of my life. Where’s Brad? Hi Brad. Anyway — I was at a party at my friend Chris’s house, and he let me borrow his guitar and do a sing-along. I think we did White Rabbit and Me and Bobby McGee and Hotel California.</p>\n\n<p>Well, here’s the problem — I have a hard time hanging on to a guitar pick. Especially after a few beers. So at one point the pick goes flying, and I’m strumming with my fingers.</p>\n\n<p>But I had a hangnail, and it got a bit aggravated as I was strumming. At the end I noticed that there was my actual blood on the guitar. I felt bad about it, but Chris was gracious, of course, and I thought that right then: that’s rock and roll.</p>\n\n<p>You can use this as metaphor. Bleeding? Keep right on playing. Maybe you won’t even notice that you’re bleeding, at least not until you stop.</p>\n\n<p>Chris told me later that the guitar cleaned up fine, so all was well.</p>\n\n<p>Okay. On to the actual talk…</p>\n\n<p>I bet most of you have heard the phrase “three chord rock n roll.” Or have heard that “rock is so great because you only need three chords.”</p>\n\n<p>What you may not realize is that it’s even easier than that: it’s three specific chords. Always the same three chords.</p>\n\n<p>They might be in any key but they’re the first, fourth, and fifth. In the key of C, the first is C, the fourth is F, and the fifth is G. In the key of A it’s A, D, and E.</p>\n\n<p>And when a song <em>does</em> have more than those three chords, it has at least those three chords. They’re the foundation for almost all pop and rock.</p>\n\n<p>One part of music is building tension and then resolving it. I’ll demonstrate on guitar.</p>\n\n<p>[On guitar] Play the first .... and you’re fine. You’re home. Play the fourth .... and there’s a little tension. Not a ton, but some. But you want to go back to the first, to home.</p>\n\n<p>Then play the fifth ... and you have maximum tension. You definitely want to go back home to the first.</p>\n\n<p>So with those three chords you have everything you need to write a thousand songs.</p>\n\n<p>Now for a little demo, I’d like to invite Laura Savino up to help me out.</p>\n\n<p>[music]</p>\n\n<p>Thanks, Laura!</p>\n\n<p>SO LET ME MAKE TWO POINTS VERY CLEAR.</p>\n\n<ol>\n<li><p>ONE. If you’re writing apps or a website or doing a podcast or whatever — if you’re just starting out and only know the equivalent of three chords, don’t worry — you can create a masterpiece with just three chords.</p></li>\n<li><p>TWO. If you do know more than three chords, you might want to consider just using those three chords anyway. People <em>love</em> those three chords. They’re appealing. They’re accessible and intimate. They work.</p></li>\n</ol>\n\n\n<h4>Slide #2: “Brimful of Asha“ by Cornershop, Asha Bhosle, and You</h4>\n\n<p>One of my personal favorite three-chord-rock songs came out in the mid-90s. Brimful of Asha by Cornershop.</p>\n\n<p>Who here knows this song?</p>\n\n<p>Let me explain what it’s about:</p>\n\n<p>Asha Bhosle sang songs for Bollywood musicals. The actresses would lip-sync, but it was her singing. She did this for over a thousand movies. Over 12,000 songs.</p>\n\n<p>Some of those songs would be released as singles. Years ago a single would come out on vinyl, as a 45. A 45 is smaller than a regular album, and it has one song on each side. The number 45 means 45 revolutions-per-minute — you’d have to set your turntable to 45 instead of the usual 33 1/3. So: a 45 is a single.</p>\n\n<p>So here’s a little bit from the song:</p>\n\n<p>[There’s dancing, behind movie screens…]</p>\n\n<p>I <em>love</em> that image. That Asha is not just singing but <em>dancing</em> as she’s singing. We never see her dancing, but that joy and engagement shows up in her performance.</p>\n\n<p>And so this song is about hope. It’s about how a song can bring some consolation and hope when people need it.</p>\n\n<p>And her name Asha actually <em>means</em> hope. Brimful of Asha — brimful of hope.</p>\n\n<p>HERE’S MY POINT.</p>\n\n<p>We're in the same business. People form an emotional connection to whatever we’re making. The things we make can bring hope to other people. Knowing that, it’s our job to be as engaged and joyful as she is as we make our things. Maybe we’re not literally dancing, but it should be the metaphorical equivalent.</p>\n\n<h4>Slide #3: Squirrel Picture</h4>\n\n<p>Squirrel!</p>\n\n<p>When I was a kid we went to a Methodist church. I haven’t been to church hardly at all since I was a kid, but I remember one cool thing from church services: the minister would pause and ask people to shake hands with the people around them.</p>\n\n<p>So here are the rules. Tell people to have a good conference, and shake hands with at least one person from another table. Stand up!</p>\n\n<h4>Slide #4: “Hallelujah” by Leonard Cohen, with Singing by James Dempsey</h4>\n\n<p>A few weeks ago I found myself in a hotel bar with a bunch of other nerds. I also found a piano. If there’s a piano, I’m going to play it. So I talked a few people — James Dempsey, Jean McDonald, Curt Clifton, and Jim Correia, into singing some songs.</p>\n\n<p>I forget who suggested Hallelujah. Might have been James. I didn’t know it very well, but I did my best. James sang, and he was awesome.</p>\n\n<p>So when I was thinking about this talk, I was thinking of doing the most beautiful possible thing I could do. So I remembered James singing this song.</p>\n\n<p>I may not be religious, but I think it’s plain that there is awesome magnificence greater than anything any human could make. It’s right outside.</p>\n\n<p>I’m not sure bears feel humble at the sight of these mountains; I’m not sure birds are awed at the vistas they fly over.</p>\n\n<p>But we do. Humans do. And knowing that we can’t measure up, it doesn’t stop us. Intead, we’re <em>inspired</em>.</p>\n\n<p>So here’s what I love about Hallelujah. It’s about trying and failing, and loving and losing — and singing Hallelujah anyway. In Cohen’s words, it may be a broken Hallelujah, but it’s still on our lips.</p>\n\n<p>James Dempsey please report to the stage.</p>\n\n<p>Everybody is encouraged to sing along. Especially to the chorus.</p>\n\n<p>[Hallelujah]</p>\n\n<h4>Slide #5: Picture of my cat Papa</h4>\n\n<p>I’m going to go around the room and have everyone introduce themselves. RULE: if anyone can’t hear, yell out.</p>",
132
+ "date_published": "2017-03-25T11:55:21-07:00"
133
+ },
134
+ {
135
+ "id": "http://inessential.com/2017/03/07/thems_thats_got_shall_get",
136
+ "url": "http://inessential.com/2017/03/07/thems_thats_got_shall_get",
137
+ "title": "Them That’s Got Shall Get",
138
+ "content_html": "<p>I try — earnestly, with good faith — to understand the Republican ideologies.</p>\n\n<p>And I think I’ve figured out one of them: they want to make life harder for poor people so that they have more incentive to become rich, and they want to make life better for rich people to reward success, since it <em>should</em> be rewarded, and since doing so provides even more incentive for poor people to become rich.</p>\n\n<p>If you look at it just the right way, you can see it’s not entirely wrong. If the government made material life pretty sweet for everybody, then some people wouldn’t bother to work to earn a living. <em>I</em> wouldn’t bother — I’d just make software and give it away for free.</p>\n\n<p>If the government made life semi-sweet — well, anybody who wants the full sweet would want a job. But some people would be fine with semi-sweet, and they wouldn’t work.</p>\n\n<p>I think that’s where Republicans stand: they think the government has made life semi-sweet, enough so that a bunch of people just <em>take</em> and don’t work. Republicans think: we need to give them an incentive to work.</p>\n\n<p>This explains the health care bill: it takes from the poor, who need incentives to work, and gives to the wealthy, who need rewards for their success. (So the Republicans think.)</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>It’s as if the Republicans have no realistic conception of what it’s like to be poor. The choice isn’t <a href=\"http://www.politico.com/story/2017/03/jason-chaffetz-new-gop-health-care-plan-235762\">between health care and an iPhone</a>, as one Republican suggested — it’s between food and rent, or worse, and forget health care and iPhones entirely.</p>\n\n<p>I was “poor” in my very early 20s. I put that in quotes because I was never in danger of starving or becoming homeless — my parents would have helped me. (They did plenty, in fact.)</p>\n\n<p>But still, even this small experience gives me some insight. I remember buying generic macaroni and cheese because I literally didn’t have enough money for Kraft. And forget hot dogs. And forget vegetables.</p>\n\n<p>I don’t mean that I had some money lying around that I’d put aside; I mean that I had a few dollars to last a week, and if I bought Kraft, which was a few dimes more, I would run out of money before the week was over.</p>\n\n<p>(My bank had a $5 minimum balance for my account. I could withdraw as little as $5 — and in those days ATMs were free — but that would have meant having more than $10 in my account to get that $5. I got so angry because I had, as I recall, $6.91 but couldn’t get at it. I remember thinking that another $5 would change my life.)</p>\n\n<p>I’m not complaining about this, or saying that I had things particularly tough. Not at all.</p>\n\n<p>I’m saying that if you take that experience, and take away any possibility of help from family, and then stretch it out for years and decades — with the inevitable issues, health and otherwise, that happen to everybody — then you have a life where getting ahead is really, really difficult. I can’t imagine; I can only try.</p>\n\n<p>But it’s no semi-sweet life. Not even close.</p>",
139
+ "date_published": "2017-03-07T18:29:27-08:00"
140
+ },
141
+ {
142
+ "id": "http://inessential.com/2017/02/23/dont_be_scared_if_you_have_to_get_an_mr",
143
+ "url": "http://inessential.com/2017/02/23/dont_be_scared_if_you_have_to_get_an_mr",
144
+ "title": "Don’t Be Scared If You Have to Get an MRI",
145
+ "content_html": "<p>“Totally normal,” said my neurologist of the results of the MRI on my head. No worries.</p>\n\n<p>I was afraid to get an MRI in the first place.</p>\n\n<p>I got a crown last week, and that didn’t worry me — it’s my ninth. Breathe the gas and just chill for a while. No big deal. It’s almost sad when it’s over.</p>\n\n<p>But I was afraid to get the MRI, because I’m slightly claustrophobic, and all I knew was that they’d put me in a big tube and then walk away.</p>\n\n<h4>How It Went</h4>\n\n<p>I didn’t have any dietary restrictions in advance. They didn’t inject me with anything. I was told to wear comfortable clothes with no metal — so I wore sweatpants, a T-shirt, and a sweatshirt. I was able to leave my rings (gold, two small diamonds) on.</p>\n\n<p>Beforehand I did a three-sixty in front of a ferrous metal detector. Then I was led through the doors with the giant warnings about extremely powerful magnets.</p>\n\n<p>I put in earplugs that the technician gave me, and then put on headphones. He asked me what music I’d like, and I replied, “80s. Bowie.” I lied down on the thing. There was a firm but not painful thing to hold my head still and give it something to rest on. Under the lower half of my legs was a foam thing that kept them elevated a little. It was comfortable.</p>\n\n<p>He told me it would take about 20 minutes. He also gave me a bulb to hold onto and to squeeze as an alert, and he said they could pause the tests if needed.</p>\n\n<p>Then he slid me in. The tube was more narrow than I expected. And for the first couple seconds I did feel panic rising a little bit, and I thought about squeezing the bulb — but I didn’t. I oriented myself and took some deep breaths.</p>\n\n<p>I was staring up at the top of the tube (I was on my back), but there was this mirror contraption (two mirrors? hard to tell) that I was looking at, and so I was looking out through the end of the tube. What I was actually seeing was a nice, calm painting on the wall — a river and some trees — and I could see the length of my body and my feet, which were free of the tube. I told myself I could scramble out on my own if I had to.</p>\n\n<p>The music started with a Bowie song — “<a href=\"https://www.youtube.com/watch?v=v--IqqusnNQ\">Life on Mars</a>.” Later there were songs by Talking Heads and similar bands. It was good to have music because I could note the passage of time that way. (I guess I was listening to a Pandora station or something similar.)</p>\n\n<p>The machine was noisy, but I had plenty enough ear protection, and the different scans had different patterns. One scan near the end included a bit of vibration. The technician talked to me through the headphones a couple times to let me know how much time was remaining. I just kept my eyes on that painting the whole time.</p>\n\n<p>I had no trouble being still, except when I had to swallow. I just did. It was otherwise comfortable. And I could have gone another 20 minutes, easy.</p>\n\n<p style=\"text-align:center\">* * *</p>\n\n\n<p>Of course, I’m lucky. I have very good insurance through Omni, and it paid for this. And, even luckier, the results were totally normal.</p>\n\n<p>Hear that, world? The inside of my head is totally normal. I don’t mind feeling good about some good news for a change.</p>\n\n<p><i>Update 4:15 pm</i>: I’ve heard that not all MRIs are so nice. They might not have the mirrors and the music. In that case, well, I’m sorry. Just remember that they won’t forget you’re in there, and they’ll let you out at the end. Stay cool.</p>",
146
+ "date_published": "2017-02-23T13:37:39-08:00"
147
+ },
148
+ {
149
+ "id": "http://inessential.com/2017/02/22/omnioutliner_essentials",
150
+ "url": "http://inessential.com/2017/02/22/omnioutliner_essentials",
151
+ "title": "OmniOutliner Essentials",
152
+ "content_html": "<p>Omni <a href=\"https://www.omnigroup.com/blog/introducing-omnioutliner-essentials-an-outliner-for-everyone\">introduces OmniOutliner Essentials</a>:</p>\n\n<blockquote><p>We didn’t want to just reach out to our existing audience; we wanted to introduce the joys and benefits of outlining to a much larger audience. We decided that meant two things: we needed to make the app much simpler, and we needed to make it much more affordable.</p></blockquote>\n\n<p>It’s in public preview now. <a href=\"https://www.omnigroup.com/omnioutliner/preview/essentials\">You can check it out</a>.</p>\n\n<p>I’ve been the junior developer on the OmniOutliner team for a couple years, and it’s a joy to work on an app that I’ve loved for years as a user. We’re not finished yet with this release, but I’m very happy with how it’s turning out.</p>\n\n<p>PS I like that Ken mentions MORE in the blog post:</p>\n\n<blockquote><p>We shipped the first beta of OmniOutliner while Mac OS X was still in beta, and doing so introduced us to a passionate community of outliners who had been using great outlining tools like <a href=\"https://en.wikipedia.org/wiki/MORE_(application)\">MORE</a> for over a decade.</p></blockquote>\n\n<p>MORE was by Living Videotext, which was <a href=\"http://scripting.com\">Dave Winer</a>’s company. Later I went to work at Dave’s company UserLand Software, which also included an outliner in its app Frontier, which I worked on. So there is a sort-of family tree connection from OmniOutliner back to MORE.</p>",
153
+ "date_published": "2017-02-22T10:17:48-08:00"
154
+ }
155
+ ]
156
+ }
157
+
158
+ ---
159
+
160
+ feed.format: json
161
+ feed.title: inessential.com
162
+ feed.url: http://inessential.com/
163
+ feed.feed_url: http://inessential.com/feed.json
164
+
165
+ feed.items[0].url: http://inessential.com/2017/05/17/json_feed
166
+ feed.items[0].guid: http://inessential.com/2017/05/17/json_feed
167
+ feed.items[0].title: JSON Feed
168
+ feed.items[0].published: DateTime.new( 2017, 5, 17, 13, 22, 14, '-7')
169
+
170
+ feed.items[1].url: http://inessential.com/2017/05/01/frontier_diary_8_when_worlds_collide
171
+ feed.items[1].guid: http://inessential.com/2017/05/01/frontier_diary_8_when_worlds_collide
172
+ feed.items[1].title: Frontier Diary #8: When Worlds Collide
173
+ feed.items[1].published: DateTime.new( 2017, 5, 1, 13, 34, 23, '-7')
@@ -0,0 +1,1197 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom"
3
+ xmlns:thr="http://purl.org/syndication/thread/1.0">
4
+ <link rel="self" href="http://intertwingly.net/blog/index.atom"/>
5
+ <link rel="hub" href="http://pubsubhubbub.appspot.com/"/>
6
+ <id>http://intertwingly.net/blog/index.atom</id>
7
+ <icon>../favicon.ico</icon>
8
+
9
+ <title>Sam Ruby</title>
10
+ <subtitle>It’s just data</subtitle>
11
+ <author>
12
+ <name>Sam Ruby</name>
13
+ <email>rubys@intertwingly.net</email>
14
+ <uri>/blog/</uri>
15
+ </author>
16
+ <updated>2017-05-26T03:36:44-07:00</updated>
17
+ <link href="/blog/"/>
18
+ <link rel="license" href="http://creativecommons.org/licenses/BSD/"/>
19
+
20
+ <entry>
21
+ <id>tag:intertwingly.net,2004:3356</id>
22
+ <link href="/blog/2017/04/07/Badges-We-dont-need-no-stinkin-badges"/>
23
+ <link rel="replies" href="3356.atom" thr:count="3" thr:updated="2017-05-25T05:04:09-07:00"/>
24
+ <title>Badges? We don't need no stinkin' badges!</title>
25
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">I found myself included in an IBM Resource Action ("RA").  I’m fine, nothing has changed.  I’m already working with a non-profit, namely the <a href="https://www.apache.org/">Apache Software Foundation</a>, and find my work there to be very rewarding.</div></summary>
26
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="158" height="61" viewBox="0 0 158 61">
27
+ <path d="M0,0v5h31v-5M35,0v5h45c0,0-4-4-9-5M88,0v5h27l-2-5M133,0l-2,5h27v-5M0,8v5h31v-5M35,8v5h49c0,0,0-3-2-5M88,8v5h30l-2-5M130,8l-2,5h30v-5M9,16v5h13v-5M44,16v5h13v-5M70,16v5h13s1,-2,1,-5M96,16v5h25l-2-5M127,16l-2,5h24v-5M9,24v5h13v-5M44,24v5h34s3-3,4-5M96,24v5h14v-3l1,3h24l1-3v3h13v-5h-25l-1,3l-1-3M9,32v5h13v-5M44,32v5h39s-2-4-4-5M96,32v5h14v-5M112,32l2,5h18l2-5M136,32v5h13v-5M9,40v5h13v-5M44,40v5h13v-5M70,40v5h15s0-3-1-5M96,40v5h14v-5M115,40l2,5h12l2-5M136,40v5h13v-5M0,48v5h31v-5M35,48v5h47s1-0,2.5-5M88,48v5h22v-5M118,48l2,5h6l2-5M136,48v5h22v-5M0,56v5h31v-5M35,56v5h38s4-1,7-5M88,56v5h22v-5M121,56l2,5l2-5M136,56v5h22v-5" fill="#1f70c1"></path>
28
+ </svg>
29
+ <p>I’ve worked from home since the late 90s.  When IBM made me go in a few years back to replace my badge, I joked that the next time I would need it was when it was time for me to turned it in.</p>
30
+ <p>Well, I was close.  I used it for the first time yesterday to go to a seminar describing what options are available to those like me who are part of an IBM Resource Action ("RA").  Which is IBM’s way of saying that my job no longer exists, and I have until June 29th to find another job within IBM or I will be offered a modest severance package, and can pick from an array of options varying from helping me find a new job, connecting me with a non-profit organization, and retraining.</p>
31
+ <p>TL;DR: I’m fine, nothing has changed.  I’m already working with a non-profit, namely the <a href="https://www.apache.org/">Apache Software Foundation</a>, and find my work there to be very rewarding.</p>
32
+ <p>And, by the way, the key advice from the seminar is to network. That happens to be something that I’m fairly good at.</p>
33
+ <p>In fact, now that I’ve told my family, my book editor, many people within IBM, and several hundred of my closest friends at the ASF — many of which want to spread the word and help me out — the inescapable conclusion is that I can’t tell all of these people without the word getting out.  So I might as well do it myself, in order to ensure that everybody gets the correct message.</p>
34
+ <p>For starters, the most likely outcome is that I’m going to simply retire.  My wife and I have planned for this for several years. This may be the nudge that was needed to make it happen.  And like many retirees, I will donate my time to work for a non-profit. I’m just ahead of the curve as I am already doing that.</p>
35
+ <p>The second most likely outcome is that I will find an equivalent job within IBM.  By equivalent, I mean an opportunity that lets me work full time on open source and open standards in general; and in particular lets me devote the time I feel necessary to the role of ASF President.  I would need to feel comfortable about that before accepting, as retiring later would mean that I would have lost the opportunity for the severance package.  The good news for those who are predisposed to root for this option is that that job has already been identified, and the management team there is working through what it takes to make it happen.  There is no guarantee that they will get HR approval, however, which is why this is listed as the second most likely outcome rather than the first.</p>
36
+ <p>And finally, the third most likely outcome is that I take a job outside of IBM.  I have a number of people saying that they will shop my résumé around.  Based on these requests, I have now produced <a href="https://intertwingly.net/resume.html">one</a>.  I am <b>not</b> looking for a headhunter, but if somebody feels that they have a perfect opportunity for me, I am willing to listen.</p>
37
+ <p>Again, whatever happens, I’m fine and nothing has changed.</p></div></content>
38
+ <updated>2017-04-07T05:07:22-07:00</updated>
39
+ </entry>
40
+
41
+ <entry>
42
+ <id>tag:intertwingly.net,2004:3355</id>
43
+ <link href="/blog/2016/07/11/Service-Workers-First-Impressions"/>
44
+ <link rel="replies" href="3355.atom" thr:count="8" thr:updated="2017-05-24T09:56:16-07:00"/>
45
+ <title>Service Workers - First Impressions</title>
46
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">Cache <code>put</code> and <code>match</code> worked right
47
+ the first time; cache <code>keys</code> not so much. Authentication is a mystery. Outline of future plans, and a call for help.</div></summary>
48
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
49
+ <metadata>
50
+ Created by potrace 1.13, written by Peter Selinger 2001-2015
51
+ </metadata>
52
+ <g transform="translate(-5,100) scale(0.035,-0.035)" fill="#000" stroke="none">
53
+ <path d="M1041 2503 c-32 -92 -63 -139 -111 -163 -61 -31 -120 -26 -213 17
54
+ l-78 37 -79 -79 c-44 -44 -80 -85 -80 -91 0 -7 16 -45 35 -85 40 -84 44 -133
55
+ 16 -195 -25 -54 -57 -77 -152 -111 l-79 -28 0 -120 0 -120 79 -28 c95 -34 127
56
+ -57 152 -111 28 -62 24 -111 -16 -195 -19 -40 -35 -78 -35 -85 0 -6 36 -47 80
57
+ -91 l79 -79 78 37 c93 43 152 48 213 17 48 -24 79 -71 111 -163 l23 -68 119 3
58
+ 119 3 28 78 c59 166 152 205 313 130 l78 -37 85 86 86 85 -35 69 c-44 87 -51
59
+ 122 -37 174 18 68 68 114 160 146 l80 27 0 122 0 122 -80 27 c-92 32 -142 78
60
+ -160 146 -14 52 -7 87 37 174 l35 69 -86 85 -85 86 -78 -37 c-161 -75 -254
61
+ -36 -313 130 l-28 78 -119 3 -119 3 -23 -68z m286 -533 c63 -31 112 -80 149
62
+ -150 27 -50 27 -220 0 -270 -38 -71 -86 -119 -153 -152 -132 -65 -274 -38
63
+ -375 70 -113 121 -116 309 -5 429 106 115 245 141 384 73z"></path>
64
+ <path d="M2306 1346 c-13 -30 -33 -59 -44 -65 -31 -17 -78 -13 -121 8 l-38 20
65
+ -43 -44 -43 -44 16 -43 c35 -91 12 -144 -72 -169 l-42 -12 3 -65 3 -65 40 -14
66
+ c60 -20 88 -57 82 -107 -2 -23 -10 -56 -18 -73 -13 -32 -12 -34 26 -73 21 -22
67
+ 44 -40 51 -40 6 0 31 9 55 19 74 32 124 8 152 -74 l14 -40 61 -3 62 -3 25 56
68
+ c19 44 32 59 59 70 32 14 40 13 92 -6 l56 -21 43 42 44 43 -20 38 c-40 79 -18
69
+ 138 62 169 l44 18 3 64 3 65 -42 12 c-85 26 -111 89 -70 170 l20 38 -47 46
70
+ -47 46 -35 -19 c-79 -43 -145 -16 -171 69 l-12 41 -64 0 -63 0 -24 -54z m161
71
+ -262 c86 -41 119 -153 69 -238 -60 -103 -193 -114 -273 -23 -120 137 38 339
72
+ 204 261z"></path>
73
+ <path d="M1586 820 c-7 -40 -31 -48 -60 -21 -27 25 -34 26 -64 3 -19 -15 -21
74
+ -22 -13 -43 14 -38 0 -53 -43 -46 -33 5 -38 3 -52 -25 -15 -28 -14 -31 6 -54
75
+ 28 -29 20 -52 -20 -61 -28 -5 -31 -9 -28 -41 3 -31 7 -37 36 -44 38 -10 42
76
+ -32 11 -64 -19 -21 -20 -24 -5 -52 15 -28 18 -30 48 -21 42 12 62 -9 47 -49
77
+ -10 -26 -8 -29 34 -53 15 -8 24 -6 41 10 31 29 45 26 62 -14 12 -29 19 -35 44
78
+ -35 25 0 32 6 44 35 17 40 31 43 62 14 21 -19 24 -20 52 -5 27 14 30 19 25 54
79
+ -6 46 3 55 46 42 29 -8 32 -6 47 22 15 29 14 31 -7 54 -27 29 -18 55 22 64 19
80
+ 5 25 13 27 42 3 32 1 36 -28 41 -41 9 -48 32 -19 63 22 24 22 26 7 52 -15 24
81
+ -22 27 -58 24 l-42 -4 4 42 c3 36 0 43 -24 58 -26 15 -28 15 -52 -7 -31 -29
82
+ -54 -22 -62 19 -6 27 -10 30 -44 30 -34 0 -38 -3 -44 -30z m91 -175 c12 -3 34
83
+ -20 48 -36 30 -36 35 -111 9 -147 -49 -71 -170 -67 -209 8 -36 70 -3 159 68
84
+ 179 32 9 41 8 84 -4z"></path>
85
+ </g>
86
+ </svg>
87
+ <p>
88
+ Successes, progress, and stumbling blocks encountered while exploring
89
+ Service Workers.
90
+ </p>
91
+
92
+ <h3 id="preface">Preface</h3>
93
+
94
+ <p>
95
+ The <a href="https://github.com/apache/whimsy/tree/master/www/board/agenda#readme">Apache
96
+ Whimsy Board Agenda tool</a> is designed to make ASF Board meetings run
97
+ more smoothly. It does this by downloading all of the provided reports and
98
+ collating them with comments, prior comments, action items, minutes, links to
99
+ prior reports, links to committee information, and the like. It provides a UI
100
+ to allow Directors and guests to enter comments. It provides a UI to allow
101
+ the Secretary to take minutes.
102
+ </p>
103
+
104
+ <p>
105
+ The tool itself is built using
106
+ <a href="https://facebook.github.io/react/">React.JS</a>. It starts by
107
+ downloading all of the reports. Navigation between reports can be done via
108
+ mouse clicks or cursor keys and doesn't involve any server interaction. As
109
+ new data is received, the window is updated.
110
+ </p>
111
+
112
+ <p>
113
+ Finally, I'm new to Service Workers so I may be doing things in a profoundly
114
+ dumb way. Any pointers would be appreciated. I am capable of RTFM and
115
+ following examples.
116
+ </p>
117
+
118
+ <h3 id="caching-json">First step - caching JSON</h3>
119
+
120
+ <p>
121
+ Some of the data (e.g., the list of ASF JIRA projects) is fetched on demand.
122
+ Generally the page is first rendered using an empty list, and then updated
123
+ once the actual list is received.
124
+ </p>
125
+
126
+ <p>
127
+ This process could be improved by caching the results received and using that
128
+ data until fresh data arrives. As the Cache API is built on promises, and
129
+ therefore asynchronous, this generally means rendering three times: once with
130
+ a empty list, then with the cache, and finally with live data.
131
+ </p>
132
+
133
+ <pre><span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> retrieve an cached object. Note: block may be dispatched twice,
134
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> once with slightly stale data <span style="color:#080;font-weight:bold">and</span> once with current data
135
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span>
136
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> <span style="color:#606">Note</span>: caches only work currently on <span style="color:#036;font-weight:bold">Firefox</span> <span style="color:#080;font-weight:bold">and</span> <span style="color:#036;font-weight:bold">Chrome</span>. All
137
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> other browsers fall back to <span style="color:#036;font-weight:bold">XMLHttpRequest</span> (<span style="color:#036;font-weight:bold">AJAX</span>).
138
+ JSONStorage.fetch = function(name, block) {
139
+ <span style="color:#080;font-weight:bold">if</span> (typeof fetch !== <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">'</span><span style="color:#D20">undefined</span><span style="color:#710">'</span></span> &amp;&amp; typeof caches !== <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">'</span><span style="color:#D20">undefined</span><span style="color:#710">'</span></span> &amp;&amp;
140
+ (location.protocol == <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">https:</span><span style="color:#710">"</span></span> || location.hostname == <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">localhost</span><span style="color:#710">"</span></span>)) {
141
+
142
+ caches.open(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">board/agenda</span><span style="color:#710">"</span></span>).then(function(cache) {
143
+ var fetched = null;
144
+ clock_counter++;
145
+
146
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> construct arguments to fetch
147
+ var args = {
148
+ <span style="color:#606">method</span>: <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">get</span><span style="color:#710">"</span></span>,
149
+ <span style="color:#606">credentials</span>: <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">include</span><span style="color:#710">"</span></span>,
150
+ <span style="color:#606">headers</span>: {<span style="color:#606">Accept</span>: <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">application/json</span><span style="color:#710">"</span></span>}
151
+ };
152
+
153
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> dispatch request
154
+ fetch(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">../json/</span><span style="color:#710">"</span></span> + name, args).then(function(response) {
155
+ cache.put(name, response.clone());
156
+
157
+ response.json().then(function(json) {
158
+ <span style="color:#080;font-weight:bold">if</span> (!fetched || <span style="color:#036;font-weight:bold">JSON</span>.stringify(fetched) != <span style="color:#036;font-weight:bold">JSON</span>.stringify(json)) {
159
+ <span style="color:#080;font-weight:bold">if</span> (!fetched) clock_counter--;
160
+ fetched = json;
161
+ <span style="color:#080;font-weight:bold">if</span> (json) block(json);
162
+ <span style="color:#036;font-weight:bold">Main</span>.refresh()
163
+ }
164
+ })
165
+ });
166
+
167
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> check cache
168
+ cache.match(name).then(function(response) {
169
+ <span style="color:#080;font-weight:bold">if</span> (response &amp;&amp; !fetched) {
170
+ response.json().then(function(json) {
171
+ clock_counter--;
172
+ fetched = json;
173
+ <span style="color:#080;font-weight:bold">if</span> (json) block(json);
174
+ <span style="color:#036;font-weight:bold">Main</span>.refresh()
175
+ })
176
+ }
177
+ })
178
+ })
179
+ } <span style="color:#080;font-weight:bold">else</span> <span style="color:#080;font-weight:bold">if</span> (typeof <span style="color:#036;font-weight:bold">XMLHttpRequest</span> !== <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">'</span><span style="color:#D20">undefined</span><span style="color:#710">'</span></span>) {
180
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> retrieve from the network only
181
+ retrieve(name, <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">json</span><span style="color:#710">"</span></span>, function(item) {<span style="color:#080;font-weight:bold">return</span> item.block})
182
+ }
183
+ }</pre>
184
+ <p>
185
+ All in all remarkably painless and completely transparent to the calling
186
+ application. Doesn't involve the activation of Service Workers, but it
187
+ doesn't have to.
188
+ </p>
189
+
190
+ <h3 id="caching-html">Second step - caching HTML</h3>
191
+
192
+ <p>
193
+ What's true for JSON should also be true for HTML. Prior to the caching
194
+ logic introduced above, and continuing to be true for browsers that don't
195
+ support the service workers caching interface, data that should appear on the
196
+ page would be missing briefly and show up a second or two later. In the case
197
+ of HTML, that data would be the entire page. This would typically be seen
198
+ both on the initial page load as well as any time a link is opened in a new
199
+ tab.
200
+ </p>
201
+
202
+ <p>
203
+ The HTML case is both simpler and more difficult. Fetching the HTML from
204
+ cache and then replacing it wholesale from the network, while possible, would
205
+ be jarring. Fortunately, there already is logic in place to update the
206
+ content of the pages based on updates received by XHR. So initially
207
+ displaying where the user last left off, as well as updating the cache,
208
+ is sufficient.
209
+ </p>
210
+
211
+ <p>
212
+ Unfortunately, it isn't quite so simple. I've included the current code below
213
+ complete with log statements and dead ends.
214
+ </p>
215
+
216
+ <pre><span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> simple hashcode to prevent authorization from leaking
217
+ var hashcode = function(s) {
218
+ <span style="color:#080;font-weight:bold">return</span> s &amp;&amp; s.split(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#710">"</span></span>).reduce(
219
+ function(a, b) {
220
+ <span style="color:#080;font-weight:bold">return</span> ((a &lt;&lt; <span style="color:#00D">5</span>) - a) + b.charCodeAt(<span style="color:#00D">0</span>)
221
+ },
222
+
223
+ <span style="color:#00D">0</span>
224
+ )
225
+ };
226
+
227
+ var status = {<span style="color:#606">auth</span>: null};
228
+
229
+ this.addEventListener(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">fetch</span><span style="color:#710">"</span></span>, function(event) {
230
+ var scope = this.registration.scope;
231
+ var url = event.request.url;
232
+ var path = url.slice(scope.length);
233
+ var auth = hashcode(event.request.headers.get(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">Authorization</span><span style="color:#710">"</span></span>));
234
+
235
+ <span style="color:#080;font-weight:bold">if</span> (<span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#808">^</span><span style="color:#D20">\d</span><span style="color:#D20">\d</span><span style="color:#D20">\d</span><span style="color:#D20">\d</span><span style="color:#808">-</span><span style="color:#D20">\d</span><span style="color:#D20">\d</span><span style="color:#808">-</span><span style="color:#D20">\d</span><span style="color:#D20">\d</span><span style="color:#404">/</span></span>/.test(path) &amp;&amp; event.request.method == <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">GET</span><span style="color:#710">"</span></span>) {
236
+ console.log(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">Handling fetch event for</span><span style="color:#710">"</span></span>, event.request.url);
237
+
238
+ event.respondWith(caches.open(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">board/agenda</span><span style="color:#710">"</span></span>).then(function(cache) {
239
+ <span style="color:#080;font-weight:bold">return</span> cache.match(path).then(function(cached) {
240
+ <span style="color:#080;font-weight:bold">if</span> (cached) console.log(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">matched</span><span style="color:#710">"</span></span>);
241
+ console.log(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">auth</span><span style="color:#710">"</span></span>, auth, status.auth);
242
+
243
+ <span style="color:#080;font-weight:bold">if</span> (!auth || auth != status.auth) {
244
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> the following doesn't work
245
+ cached = new Response(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">Unauthorized</span><span style="color:#710">"</span></span>, {
246
+ <span style="color:#606">status</span>: <span style="color:#00D">401</span>,
247
+ <span style="color:#606">statusText</span>: <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">Unauthorized</span><span style="color:#710">"</span></span>,
248
+ <span style="color:#606">headers</span>: {<span style="color:#606"><span style="color:#404">"</span><span>WWW-Authenticate</span><span style="color:#404">"</span></span>: <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">Basic realm=</span><span style="color:#710">"</span></span><span style="color:#036;font-weight:bold">ASF</span> <span style="color:#036;font-weight:bold">Members</span> <span style="color:#080;font-weight:bold">and</span> <span style="color:#036;font-weight:bold">Officers</span><span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#710">"</span></span>}
249
+ });
250
+
251
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> <span style="color:#606">fallback</span>: ignore cache <span style="color:#080;font-weight:bold">unless</span> authorized
252
+ cached = null
253
+ };
254
+
255
+ <span style="color:#080;font-weight:bold">if</span> (cached) console.log(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">serving from cache</span><span style="color:#710">"</span></span>);
256
+
257
+ var network = fetch(event.request).then(function(response) {
258
+ <span style="color:#080;font-weight:bold">if</span> (!cached) console.log(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">fetching from network</span><span style="color:#710">"</span></span>);
259
+ <span style="color:#080;font-weight:bold">if</span> (cached) console.log(<span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">"</span><span style="color:#D20">updating cache</span><span style="color:#710">"</span></span>);
260
+ console.log(response);
261
+ <span style="color:#080;font-weight:bold">if</span> (response.ok) cache.put(path, response.clone());
262
+ status.auth = auth;
263
+ <span style="color:#080;font-weight:bold">return</span> response
264
+ });
265
+
266
+ <span style="color:#080;font-weight:bold">return</span> cached || network
267
+ })
268
+ }))
269
+ } <span style="color:#080;font-weight:bold">else</span> <span style="color:#080;font-weight:bold">if</span> (auth) {
270
+ <span style="background-color:hsla(300,100%,50%,0.06)"><span style="color:#404">/</span><span style="color:#404">/</span></span> capture authorization from other pages, <span style="color:#080;font-weight:bold">if</span> provided
271
+ status.auth = auth
272
+ }
273
+ })</pre>
274
+ <p>
275
+ The primary problem is that the board agenda tool requires authentication to
276
+ use as the data presented may contain Apache Software Foundation confidential
277
+ information.
278
+ </p>
279
+ <p>
280
+ Without accounting for this, what often would be placed into the cache would
281
+ be the HTTP <code>401</code> challenge response. That's not desirable.
282
+ </p>
283
+ <p>
284
+ Attempting to force the return of a challenge when an Authorization header is not present results in the display of the challenge response. Again, not what we want.
285
+ </p>
286
+ <p>
287
+ Falling back to only providing the cached data when the Authorization header
288
+ is present (and matches the one used for the cache) results in the cache being
289
+ used sometimes with Firefox. And, unfortunately, never with Chrome.
290
+ </p>
291
+ <p>
292
+ A secondary problem, of lesser importance, is that the cache never gets
293
+ updated if the service worker responds with a cache copy. Of if it does,
294
+ the <code>console.log</code> messages aren't getting executed or aren't
295
+ producing output.
296
+ </p>
297
+
298
+ <h3 id="monitoring">Third step - monitoring</h3>
299
+
300
+ <p>
301
+ To help with debugging, it occurred to me that it would make sense to produce
302
+ a page that shows Service Worker and Cache status.
303
+ </p>
304
+
305
+ <ul>
306
+ <li>
307
+ <p>
308
+ For service workers, there was no problems, but the results were
309
+ underwhelming. I only got information back about my service worker even
310
+ though I had several activated by this point by virtue of running
311
+ various demos. That's not a problem, as that's all I needed. The only
312
+ information I could get was the state of the service worker. But even
313
+ so, I could use this as a building block to enable users to send a
314
+ message to the service worker and/or unregister it. See plans below for
315
+ more details.
316
+ </p>
317
+ </li>
318
+
319
+ <li>
320
+ <p>
321
+ For caches, I simply couldn't get it to work. For example, I tried
322
+ adding the following line immediate after the <code>cache.put</code>
323
+ line in the first code snippet:
324
+ </p>
325
+ <pre>console.log cache.keys()</pre>
326
+ <p>
327
+ The result was an empty list (<code>[]</code>) on both Firefox and
328
+ Chrome. This is problematic on a number of levels, not the least of
329
+ which being that the interface is defined to return a promise and Arrays
330
+ in JavaScript don't respond to then.
331
+ </p>
332
+ <p>References:</p>
333
+ <ul>
334
+ <li>
335
+ <a href="https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-keys">Service Workers Nightly</a>
336
+ </li>
337
+ <li>
338
+ <a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache/keys">Cache.keys() - Web APIs | MDN</a>
339
+ </li>
340
+ </ul>
341
+ </li>
342
+ </ul>
343
+
344
+ <h3 id="plans">Plans</h3>
345
+
346
+ <p>
347
+ One thing I haven't explored yet is replacing the fetch call with one with
348
+ different values for the request mode and credentials mode. I figured I would
349
+ ask for guidance before proceeding down that path.
350
+ </p>
351
+
352
+ <p>
353
+ Once caching HTML is mastered, caching related artifacts like stylesheets and
354
+ javascripts would be in order. An online fallfack approach would likely be
355
+ the best match.
356
+ </p>
357
+
358
+ <p>
359
+ After that, the next order of business would be queuing of updates while
360
+ offline. While in general, that would be a hard problem, in this case as user
361
+ operations are limited by role and generaally to editing their own changes,
362
+ it should be manageable.
363
+ </p></div></content>
364
+ <updated>2016-07-11T11:27:29-07:00</updated>
365
+ </entry>
366
+
367
+ <entry>
368
+ <id>tag:intertwingly.net,2004:3354</id>
369
+ <link href="/blog/2015/09/24/FacePalm"/>
370
+ <link rel="replies" href="3354.atom" thr:count="2" thr:updated="2017-05-26T02:31:05-07:00"/>
371
+ <title>FacePalm</title>
372
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
373
+ <rect fill="#D22" x="0" y="3" height="95" width="95" rx="15"></rect>
374
+ <circle cx="18" cy="81" r="9" fill="#FFF"></circle>
375
+ <path d="M48,84s0-33-33-33 M75,84s0-60-60-60"
376
+ stroke-linecap="round" stroke-width="15" stroke="#FFF" fill="none"></path>
377
+ </svg>
378
+ <p><a href="https://developers.facebook.com/docs/instant-articles/automated-publishing">Automated Publishing with Instant Articles</a></p>
379
+ <p><code>&lt;description&gt;</code> A summary of your article, in <b>plain text</b> form.</p>
380
+ <p><code>&lt;pubDate&gt;</code> The date of the article’s publication, in <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO-8601 format.</a></p>
381
+ <p>Related: <a href="http://www.intertwingly.net/blog/2006/03/28/plaintext">plaintext</a>, <a href="http://intertwingly.net/blog/2006/05/01/May-Day">May Day</a>, <a href="http://intertwingly.net/blog/2006/06/01/June-Bug">June Bug</a>, <a href="http://intertwingly.net/blog/2006/07/14/Another-Month">Another Month</a>, and numerous others.</p></div></content>
382
+ <updated>2015-09-24T08:44:23-07:00</updated>
383
+ </entry>
384
+
385
+ <entry>
386
+ <id>tag:intertwingly.net,2004:3353</id>
387
+ <link href="/blog/2015/05/18/Brief-history-of-the-ASF-Board-Agenda-tool"/>
388
+ <link rel="replies" href="3353.atom" thr:count="6" thr:updated="2017-05-25T18:58:38-07:00"/>
389
+ <title>Brief history of the ASF Board Agenda tool</title>
390
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>the current implementation is a lot more fun to develop and easier to maintain than prior versions.  As an example, if it were decided that the moment the secretary clicked the ‘timestamp` button on the 'Call to order’ page, all comment buttons are to be removed from all windows and all comment modal dialogs are to be closed, this could be implemented using a single if statement as the event is already propagated, and a re-render is already triggered.  All that would be required is to change the conditions under which the comment button appears.</p>
391
+ <p>The <a href="https://github.com/rubys/whimsy-agenda#readme">board agenda tool</a> has been tested on Linux, Mac OS/X, Vagrant, and Docker.  It contains a suite of tests.</p></div></summary>
392
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="100" height="100" viewBox="0 0 100 100">
393
+ <path d="M34,38a16,16,0,1,0,0,24l32-24a16,16,0,1,1,0,24M40,43l20,15"
394
+ stroke="#44B74A" stroke-width="4" fill="none"></path>
395
+ </svg>
396
+ <p>The gold standard of server side web applications is Model, View, Controller.  Early versions of this tool was not written that way: it was a CGI script that grew like a weed.  Over time, some <a href="https://jquery.com/">JQuery</a> effects were added.</p>
397
+ <p>The first major rewrite was done using <a href="https://angularjs.org/">Angular.js</a> and <a href="http://getbootstrap.com/">Bootstrap</a>.  These frameworks enabled me to do things I had never done before.  It also required me to write code that watched for changes, and to ensure that changes were applied in place (specifically arrays and hashes could not be replaced, they had to be updated).</p>
398
+ <p>While Angular.js used terms like Directives, Filters, and Services, the overall effect was to impose a structure on the client side application.  As with most things, this structure was both constraining and freeing.</p>
399
+ <p>The current rewrite replaces Angular.js with <a href="https://facebook.github.io/react/">React.js</a>.  Gone is all watches and the need to update things in place.  In its place is a policy of “rerender everything” whenever an event (a keystroke, a mouse click, a server side event) occurs.  With React.JS, rerendering everything is efficient as React computes a delta and then only applies the delta to the DOM.  React.JS does provide a suggested architecture, namely Flux, that minimizes the need to rerender everything, but in practice I have not found that necessary.</p>
400
+ <p>To illustrate, if you bring up the “Call to order” page and press and hold down the right arrow key, every page of the agenda will be flashed up and promptly replaced.</p>
401
+ <p>The overall resulting flow is as follows: when a page is fetched the response starts out with a pre-rendered representation (simple HTML), followed by the scripts needed to produce that page, followed by the data used by those scripts.  This ensures that the data is presented promptly, then become reactive to input and events.</p>
402
+ <p>The resulting architecture isn’t MVC on either the client or the server.  Instead, V and C get mushed together, and a unified client/server event stream is added.</p>
403
+ <p>Events are received from the server using <a href="http://www.w3.org/TR/eventsource/">Server Sent Events</a>.  This is <a href="http://caniuse.com/#feat=eventsource">widely implemented</a>, and has a solid <a href="https://github.com/Yaffle/EventSource/">polyfill</a> for browsers (most notably, IE) that haven’t implemented this standard.  Its one way data flow is a good fit for React.js.</p>
404
+ <p>Events are generally triggered by actions on a client browser window somewhere (typically a mouse click) resulting in a HTTP GET or POST request being sent to the server, but can also be triggered by file system changes on the server (example: a cron job does a svn update, which causes the agenda to contain new data).</p>
405
+ <p>A single event-stream is maintained per browser, and that process is responsible for propagating updates to all tabs and windows.  Events can be sent to all clients, or only clients authenticated with a given user id.  This enables my pending updates to be immediately reflected on all of my tabs and windows but not affect others.  The result of an event is to update one or more models, and then trigger a re-render.</p>
406
+ <p>Models on both the client and server are simple classes.  Class methods operate on the entity as a whole (example: write the whole agenda to disk on the server, or provide an index for the agenda on the client).  Instance methods refer to an individual item (example: an agenda item).</p>
407
+ <p>What’s left is React Components on the client and actions on the server.</p>
408
+ <p>React components have a render method.  That method has full (read-only) access to client models, and can do if statements, iterate over result, and (generally minor) computations.  More extensive computations should be refactored to other methods in the component when limited in scope to a single component, or to the client model otherwise.  The one limitation that is enforced is that render methods can not directly or indirectly change state.  A predefined <a href="https://facebook.github.io/react/docs/component-specs.html">life-cycle</a> is defined.  Other methods can be added, for example methods to handle onClick events.</p>
409
+ <p>These methods can trigger HTTP POST and GET requests (the convenience method I provide for the latter is called fetch instead).  These run small scripts on the server that may update models, generate events, and return JSON.</p>
410
+ <p>Taken together, the current implementation is a lot more fun to develop and easier to maintain than prior versions.  As an example, if it were decided that the moment the secretary clicked the ‘timestamp` button on the 'Call to order’ page, all comment buttons are to be removed from all windows and all comment modal dialogs are to be closed, this could be implemented using a single if statement as the event is already propagated, and a re-render is already triggered.  All that would be required is to change the conditions under which the comment button appears.</p>
411
+ <p>The <a href="https://github.com/rubys/whimsy-agenda#readme">board agenda tool</a> has been tested on Linux, Mac OS/X, Vagrant, and Docker.  It contains a suite of tests.</p></div></content>
412
+ <updated>2015-05-18T09:15:15-07:00</updated>
413
+ </entry>
414
+
415
+ <entry>
416
+ <id>tag:intertwingly.net,2004:3352</id>
417
+ <link href="/blog/2015/04/02/Spartan-Test-Results"/>
418
+ <link rel="replies" href="3352.atom" thr:count="3" thr:updated="2017-05-24T10:09:45-07:00"/>
419
+ <title>Spartan Test Results</title>
420
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
421
+ <g stroke-width="5" stroke="#030092" fill="none">
422
+ <circle cx="50" cy="50" r="46"></circle>
423
+ <path d="M8,35h84M8,65h84"></path>
424
+ <ellipse cx="50" cy="50" rx="20" ry="43"></ellipse>
425
+ </g>
426
+ </svg>
427
+ <p>I replaced IE results with Spartan results in my <a href="https://url.spec.whatwg.org/interop/test-results/">urltests</a>.  Other than the user agent string, nothing changed.</p>
428
+ <p>Following are selected examples where three out of four of the top browsers agree, identified by the odd browser out:</p>
429
+ <ul>
430
+ <li><a href="https://url.spec.whatwg.org/interop/test-results/53d49202f1?select=current&amp;baseline=chrome">Chrome</a></li>
431
+ <li><a href="https://url.spec.whatwg.org/interop/test-results/15341d9fab?select=current&amp;baseline=firefox">Firefox</a></li>
432
+ <li><a href="https://url.spec.whatwg.org/interop/test-results/d7d52bebd0?select=current&amp;baseline=safari">Safari</a></li>
433
+ <li><a href="https://url.spec.whatwg.org/interop/test-results/8bb3c95bce?select=current&amp;baseline=spartan">Spartan</a></li>
434
+ </ul></div></content>
435
+ <updated>2015-04-02T16:54:22-07:00</updated>
436
+ </entry>
437
+
438
+ <entry>
439
+ <id>tag:intertwingly.net,2004:3351</id>
440
+ <link href="/blog/2015/04/01/Ruby2JS-2-0"/>
441
+ <link rel="replies" href="3351.atom" thr:count="3" thr:updated="2017-05-24T03:49:35-07:00"/>
442
+ <title>Ruby2JS 2.0</title>
443
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="100" height="100" viewBox="0 0 100 100">
444
+ <path d='M20,100l74-5l6-75zM61,35l37-2l-29-24z' fill='#b11'></path>
445
+ <path d='M21,100l74-5l-47-4zM98,33c4-12,5-29-14-33l-15,9l29,24z' fill='#811'></path>
446
+ <path d='M7,67l14,33l11-38z' fill='#d44'></path>
447
+ <path d='M29,61l42,13l-10-42zM56,0h28l-16,10zM1,51l-1,29l7-13z' fill='#c22'></path>
448
+ <path d='M32,61l39,13c-14,13-30,24-50,26z' fill='#a00'></path>
449
+ <path d='M61,35l10,39l17-23zM32,61l16,30c9-5,16-11,23-17l-39-13z' fill='#900'></path>
450
+ <path d='M61,35l27,17l10-20l-37,3z' fill='#800'></path>
451
+ <path d='M71,74l23,21l-6-44zM0,80c1,19,15,20,21,20l-14-33l-7,13zM7,67l-2,26c4,6,9,7,15,6c-4-11-13-32-13-32zM69,9l30,4c-1-7-6-11-15-13l-15,9z' fill='#911'></path>
452
+ <path
453
+ d='M1,51l6,16l25-5l29-27l8-26l-13-9l-22,8c-6,7-20,19-20,19c-1,1-9,16-13,24z'
454
+ fill='#f84'></path>
455
+ <path d='M21,21c15-14,34-23,42-16c7,8-1,26-16,40c-14,15-33,24-41,17c-7-7,1-26,15-41z' fill='#F0DB4F'></path>
456
+ <g transform="rotate(307,33,12),scale(0.45)">
457
+ <path d='M26,84l8-5c1,3,3,5,6,5c3,0,5-1,5-6v-32h9v32c0,10-5,14-14,14c-7,0-11-4-14-8' id='j'></path>
458
+ <path d='M60,83l7-5c2,3,5,6,9,6c4,0,7-2,7-5c0-3-3-4-7-6l-2-1c-7-3-12-7-12-14c0-7,6-13,14-13c6,0,10,2,13,8l-7,5c-1-3-3-4-6-4c-3,0-4,1-4,4c0,2,1,4,5,5l3,1c8,4,12,7,12,15c0,9-6,13-15,13c-9,0-15-4-17-9' id='s'></path>
459
+ </g>
460
+ </svg>
461
+ <p>I’ve released <a href="https://github.com/rubys/ruby2js#readme">Ruby2JS</a> version 2.0.  Key new features:</p>
462
+ <ul>
463
+ <li>Line comment support.  More specifically, comments associated with statements are copied to the output.  Comments within statements are still omitted.</li>
464
+ <li><a href="https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit">Source Map</a> support.  This enables debugging of generated JavaScript using the Ruby source.</li>
465
+ </ul>
466
+
467
+ <p>The <a href="https://github.com/rubys/whimsy-agenda#readme">Whimsy Agenda</a> rewrite-in-progress (previously based on Angular.js, now being rebased on React.js) can be used to explore both of these features.</p></div></content>
468
+ <updated>2015-04-01T07:26:31-07:00</updated>
469
+ </entry>
470
+
471
+ <entry>
472
+ <id>tag:intertwingly.net,2004:3350</id>
473
+ <link href="/blog/2015/02/11/React-rb-updates"/>
474
+ <link rel="replies" href="3350.atom" thr:count="2" thr:updated="2017-05-26T03:36:17-07:00"/>
475
+ <title>React.rb updates</title>
476
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>I’ve made a number of updates to the demos.  The <a href="http://facebook.github.io/react/docs/tutorial.html">tutorial</a> demo has been updated to do server side rendering.  This means that it is able to be used by clients which either don’t support or have turned off JavaScript.  </p>
477
+ <p>The second demo is a calendar.  Unlike the tutorial which is a single file, this application is organized in a manner more consistent with how I expect projects to be organized.</p></div></summary>
478
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
479
+ <g transform="translate(50,50)">
480
+ <circle fill="#00D8FF" r="8"></circle>
481
+ <g fill="none" stroke="#00D8FF" stroke-width="4">
482
+ <ellipse rx="45" ry="17"></ellipse>
483
+ <ellipse rx="45" ry="17" transform="rotate(60)"></ellipse>
484
+ <ellipse rx="45" ry="17" transform="rotate(120)"></ellipse>
485
+ </g>
486
+ </g>
487
+ </svg>
488
+ <p>I’ve made a number of updates to the demos.  The <a href="http://facebook.github.io/react/docs/tutorial.html">tutorial</a> demo has been updated to do server side rendering.  This means that it is able to be used by clients which either don’t support or have turned off JavaScript.  To run:</p>
489
+ <pre class="code">git clone https://github.com/rubys/ruby2js.git
490
+ cd ruby2js/demo
491
+ bundle update
492
+ ruby react-tutorial.rb</pre>
493
+ <p>Visit the URL (typically <a href="http://localhost:4567/">http://localhost:4567/</a>) and enter a comment.  Visit the same URL in a different tab or a different browser and enter another comment.  Switch back to the original browser/tab.  If you have client side JavaScript disabled, you will need to hit refresh.</p>
494
+ <p>The second demo is a calendar.  To get started:</p>
495
+ <pre class="code">git clone https://github.com/rubys/wunderbar.git
496
+ cd wunderbar/demo/calendar
497
+ bundle update
498
+ rackup</pre>
499
+ <p>Visit the URL (typically <a href="http://localhost:9292/">http://localhost:9292/</a>). This will take you to the current month.  Left and right arrows will take you different months (and update the URL).  Unlike the tutorial which is a single file, this application is organized in a manner more consistent with how I expect projects to be organized.</p></div></content>
500
+ <updated>2015-02-11T15:10:31-08:00</updated>
501
+ </entry>
502
+
503
+ <entry>
504
+ <id>tag:intertwingly.net,2004:3349</id>
505
+ <link href="/blog/2015/02/03/DSL-for-JavaScript"/>
506
+ <link rel="replies" href="3349.atom" thr:count="0"/>
507
+ <title>DSL for JavaScript</title>
508
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p><a href="https://twitter.com/jashkenas/status/562635888753377281">Jeremy Ashkenas</a>: <em>“work towards building a language that is to ES6 as CoffeeScript is to ES5”… close, but—do it for [ES6+HTML+CSS], and you’ll win ;)</em></p>
509
+ <p>It occurs to me that there is a shortcut available.  Let a library like React replace [ES6+HTML+CSS].  Then build a <a href="http://en.wikipedia.org/wiki/Domain-specific_language">DSL</a> for that library.</p></div></summary>
510
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
511
+ <path d="M4,14h92" stroke="#4682b4" stroke-width="5"></path>
512
+ <text x="50" y="90" font-size="90" fill="#5f9ea0" font-family="serif" text-anchor="middle"><![CDATA[W]]></text>
513
+ </svg>
514
+ <p><a href="https://twitter.com/jashkenas/status/562635888753377281"><cite>Jeremy Ashkenas</cite></a>: <em>“work towards building a language that is to ES6 as CoffeeScript is to ES5”… close, but—do it for [ES6+HTML+CSS], and you’ll win ;)</em></p>
515
+ <p>It occurs to me that there is a shortcut available.  Let a library like React replace [ES6+HTML+CSS].  Then build a <a href="http://en.wikipedia.org/wiki/Domain-specific_language">DSL</a> for that library.</p>
516
+ <p>JavaScript isn’t exactly known for its ability to build DSLs.  Ruby, however, <a href="http://jroller.com/rolsen/entry/building_a_dsl_in_ruby">is</a>.  And has an excellent <a href="https://github.com/whitequark/parser">parser</a> library.  By <a href="https://github.com/rubys/ruby2js#filters">transforming</a> the <a href="http://en.wikipedia.org/wiki/Abstract_syntax_tree">AST</a>, I can convert <a href="https://svn.apache.org/repos/infra/infrastructure/trunk/projects/whimsy/www/calendar-demo/views/calendar.js.rb">calendar.js.rb</a> into <a href="http://intertwingly.net/stories/2015/02/02/calendar-demo/calendar.js">calendar.js</a>.</p>
517
+ <p>In the process, I start by replacing <a href="http://facebook.github.io/react/docs/jsx-in-depth.html">JSX</a> with a <a href="https://github.com/rubys/wunderbar/#wunderbar-easy-html5-applications">library</a> which was inspired by <a href="https://github.com/jimweirich/builder#readme">Builder</a>, <a href="http://markaby.rubyforge.org/">Markaby</a>, and <a href="https://github.com/ahoward/tagz">Tagz</a>.  These libraries, in turn were presumably inspired by earlier works like <a href="http://perldoc.perl.org/CGI.html#CREATING-STANDARD-HTML-ELEMENTS:">Perl’s CGI</a>.</p>
518
+ <p>But there is more.  JSX can’t directly express iteration.  Look at <a href="https://github.com/reactjs/react-tutorial/blob/85a92a09a9dbfbde6c74bf6fbc9cfa2919775d61/public/scripts/example.js#L81">CommentList</a> from the <a href="http://facebook.github.io/react/docs/tutorial.html">React tutorial</a>.  Instead you build up a list, and then subsequently wrap that list.  For nested lists, it appears worthwhile to split out separate components.  There is nothing wrong with doing that, but I will suggest that the primary reason to split out a component shouldn’t be to pander to the limitations of the programming language syntax.</p>
519
+ <p>In Ruby you <b>can</b> directly express iteration.  So where a comment box in the tutorial takes four classes, an entire calendar month can be expressed in one.</p>
520
+ <p>And there is even more.  <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions">Functions</a> in JavaScript are the swiss army knives of programming language features.  The can be used to express classes, blocks, lambdas, procs.  But this flexiblity comes at a <a href="http://alistapart.com/article/getoutbindingsituations">price</a>.  Ruby2JS can detect when idioms like <a href="http://stackoverflow.com/questions/962033/what-underlies-this-javascript-idiom-var-self-this">var self=this</a> are needed and automatically apply them.</p>
521
+ <p>The net is that I can write smaller, more understandable code.  And in the process focus more on the problem I’m trying to solve.</p>
522
+ <p>Like with <a href="http://coffeescript.org/">CoffeeScript</a>, <em>"It’s just JavaScript"</em>. The code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime.  You can use any existing JavaScript library seamlessly from Ruby2JS (and vice-versa). The compiled output is readable and pretty-printed, will work in every JavaScript runtime, and tends to run as fast or faster than the equivalent handwritten JavaScript.</p>
523
+ <p>Now I don’t expect to have the success or <a href="https://twitter.com/raganwald/status/555386257233027073">impact</a> that CoffeeScript has had.  But I can say that I’m having fun.  And in the process, I’m seeing the benefits with applications I write.</p></div></content>
524
+ <updated>2015-02-03T16:50:18-08:00</updated>
525
+ </entry>
526
+
527
+ <entry>
528
+ <id>tag:intertwingly.net,2004:3348</id>
529
+ <link href="/blog/2015/02/02/Web-Components"/>
530
+ <link rel="replies" href="3348.atom" thr:count="17" thr:updated="2017-05-20T09:37:23-07:00"/>
531
+ <title>Web Components</title>
532
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p><a href="https://twitter.com/brianleroux/status/561594569913950208">Brian Leroux</a>: <em>ES6 and Web Components</em></p>
533
+ <p>My take is that this talk lumps React in with others based on when it was introduced; but that it is fundamentally different from, say Angular.js as Angular.js is from jQuery.</p></div></summary>
534
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
535
+ <g transform="translate(50,50)">
536
+ <circle fill="#00D8FF" r="8"></circle>
537
+ <g fill="none" stroke="#00D8FF" stroke-width="4">
538
+ <ellipse rx="45" ry="17"></ellipse>
539
+ <ellipse rx="45" ry="17" transform="rotate(60)"></ellipse>
540
+ <ellipse rx="45" ry="17" transform="rotate(120)"></ellipse>
541
+ </g>
542
+ </g>
543
+ </svg>
544
+ <p><a href="https://twitter.com/brianleroux/status/561594569913950208"><cite>Brian Leroux</cite></a>: <em>ES6 and Web Components</em></p>
545
+ <p>Good overview.  Issues:</p>
546
+ <ul>
547
+ <li>YUI is an example of a key problem w/ corp stewardship; Angular, Polymer, React all OK though?</li>
548
+ <li>HTML Imports in trouble as Mozilla doesn’t want to implement; Custom Elements OK even though Chrome is the only implementation?</li>
549
+ <li>Overall, Brian mentions four specifications, and crosses off three.  Why not all four?</li>
550
+ </ul>
551
+
552
+ <p>My take is that this talk lumps React in with others based on when it was introduced; but that it is fundamentally different from, say Angular.js as Angular.js is from jQuery.  Compared to the alternatives, react is more imperative, and is based on a virtual DOM.  It also can run in both the server and the client.</p>
553
+ <p>Brian suggests that you view source on <a href="http://brian.io/date-today/">http://brian.io/date-today/</a>.  What you don’t see when you do that is today’s date.  I’d suggest that the ideal would be a page where you do see today’s date — even if JavaScript is disabled.  And for you to be able to interact with that page in ways that involve the server.</p>
554
+ <p>I have my own page on which I would suggest that you view source: <del><a href="https://whimsy.apache.org/calendar-demo">calendar-demo</a></del> (<strong>Update:</strong> that site is down, try <a href="http://intertwingly.net/stories/2015/02/02/calendar-demo/2015/02">this static snapshot</a>).  Use the left and right arrow buttons to go to the previous and next months.  Viewing source reveals that the page is delivered pre-rendered, and only after the content is delivered are script libraries loaded.  Traversing to the next and previous months are pretty snappy despite the fact that there has been no optimization: in particular, there are no anticipatory prefetches.  Nor is data retained should you go back to a previous month.  Neither of these changes would be hard to implement.</p>
555
+ <p>Source is available in <a href="https://svn.apache.org/repos/infra/infrastructure/trunk/projects/whimsy/www/calendar-demo">svn</a>.  Check it out, do a bundle update to get the dependencies, run rake if you want to run a few tests, and run rackup to start a local server.</p>
556
+ <p>I must say that being able to define a component with all of the rendering, client, and server logic in one place is very appealing to me.</p>
557
+ <p>Brian suggests authoring source in ES6, and targeting ES5.  My preference would be to work towards building a language that is to ES6 as CoffeeScript is to ES5.  At the moment, my experimentation along those lines is happening in <a href="https://rubygems.org/gems/ruby2js">Ruby2JS</a>.</p>
558
+ <p><a href="https://www.youtube.com/watch?v=7rDsRXj9-cU">React Native</a> looks worth watching.  Perhaps as my calendar is using flexbox, I will be able to quickly build an Android or IOS equivalent.</p></div></content>
559
+ <updated>2015-02-02T14:28:32-08:00</updated>
560
+ </entry>
561
+
562
+ <entry>
563
+ <id>tag:intertwingly.net,2004:3347</id>
564
+ <link href="/blog/2015/01/28/Email-addresses"/>
565
+ <link rel="replies" href="3347.atom" thr:count="1" thr:updated="2017-05-17T22:08:32-07:00"/>
566
+ <title>Email addresses</title>
567
+ <summary>I have been telling all non-IBMers to not use my ibm.com email address for years, but this advice is routinely ignored.  I’ve repeated the reaons behind why I ask this enough times that it makes sense for me to post the reasons in one place so that I can point to it.</summary>
568
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="167" height="60" viewBox="0 0 167 60">
569
+ <rect x='32' y='15' fill='#f3b457' rx='3' height='37' width='113'></rect>
570
+ <g stroke='#FFF' stroke-width='2'>
571
+ <path d='M38,9c-3,17-11,31-12,33c11,4,21,9,31,15c3-8,5-16,7-24-8-11-17-17-26-24' fill='#64a15a'></path>
572
+ <path d='M38,9c5,12,8,20,11,30l15-6' fill='#64a15a'></path>
573
+ <path d='M53,14c10,12,20,24,24,38c10-8,20-16,29-22-1-15-8-23-17-29z' fill='#57a295'></path>
574
+ <path d='M53,14c13,0,26,2,38,6c0-6,1-13-2-20' fill='#57a295'></path>
575
+ <path d='M91,33c11-7,22-13,38-15c17,6,16,11,21,17-14,3-25,14-35,23-7-16-16-18-24-25z' fill='#d37736'></path>
576
+ <path d='M91,33c14-2,26-1,39,0v-14' fill='#d37736'></path>
577
+ </g>
578
+ <path d='M4,24l5,4-5,4h7v-8z' fill='#FFF200'></path>
579
+ <path d='M25,27l-5-3h-16l9,4-9,4h16l5-3z' fill='#d4477e'></path>
580
+ <path d='M27,28l-4-2h-14l4,2-4,2h14l5-3z' fill='#e55d9c'></path>
581
+ <path d='M61,27h38l-4,2h-32zM31,27h-28l-3,1l4,1h27zM122,27h33v2h-31z' fill='#303f7a'></path>
582
+ <path d='M151,31l17-3-17-3c4,2,4,4,0,6' fill='#303f7a'></path>
583
+ </svg>
584
+ <p>I have been telling all non-IBMers to not use my ibm.com email address for years, but this advice is routinely ignored.  I’ve repeated the reaons behind why I ask this enough times that it makes sense for me to post the reasons in one place so that I can point to it.</p>
585
+ <p>The back story is that 15 years ago I wrote some open source code in a programming language called Java.  I don’t use that language much any more, but I understand that it remains popular in some circles.  In any case, javadoc style comments encouraged sharing your email address, and my employer discouraged me from doing anything that would hide my relationship with them, so my email address was put out on the web.</p>
586
+ <p>The inevitable result is that I’m deluged with spam, most in languages I am not familiar with.</p>
587
+ <p>My personal email I have control over and the spam tools (all open source) I use are largely effective.  I don’t have that option with my corporate email.  As others within IBM don’t have this problem, I am clearly an outlier.</p>
588
+ <p>Over time, I was missing enough important work-related emails that I tought myself enough LotusScript to write a script that I can invoke as an ‘Action’.  This script identifies emails that were sent from outside of Lotus Notes and places them into a separate folder.  If I am alerted to the presence of a single email, and given enough information (like senders name and time it was sent) I can generally find the email; but in general people should assume that emails sent to my corporate email address from outside of IBM are never seen by me.</p>
589
+ <p>Another downside of this is that some of my IBM email is sent from service machines that don’t interface directly with Lotus Notes.  That means that I miss some important updates.  And important reminders.  Eventually such reminders copy my manager, who sends them on to me.</p>
590
+ <p>Apparently there is plans in the works to migrate corporate email to the “cloud”.  Perhaps that will be better.  Perhaps I will need to find a way to reimplement my filter or equivalent.  Or perhaps it won’t be something that I <a href="http://www.cringely.com/2015/01/22/ibms-reorg-hell-launches-next-week/">won’t need to worry about any more</a>.</p></div></content>
591
+ <updated>2015-01-28T08:48:39-08:00</updated>
592
+ </entry>
593
+
594
+ <entry>
595
+ <id>tag:intertwingly.net,2004:3346</id>
596
+ <link href="/blog/2015/01/22/React-rb"/>
597
+ <link rel="replies" href="3346.atom" thr:count="9" thr:updated="2017-05-26T01:55:48-07:00"/>
598
+ <title>React.rb</title>
599
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">Having determined that Angular.js is overkill for my <a href="http://intertwingly.net/blog/2014/12/19/Weblog-Software-Rewrite-Underway">blog rewrite</a>, I started looking more closely at <a href="http://facebook.github.io/react/">React</a>.  It occurred to me that I could do better than <a href="http://facebook.github.io/jsx/">JSX</a>, so I wrote a <a href="https://github.com/rubys/ruby2js">Ruby2JS</a> filter.  Compare for yourself.</div></summary>
600
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
601
+ <g transform="translate(50,50)">
602
+ <circle fill="#00D8FF" r="8"></circle>
603
+ <g fill="none" stroke="#00D8FF" stroke-width="4">
604
+ <ellipse rx="45" ry="17"></ellipse>
605
+ <ellipse rx="45" ry="17" transform="rotate(60)"></ellipse>
606
+ <ellipse rx="45" ry="17" transform="rotate(120)"></ellipse>
607
+ </g>
608
+ </g>
609
+ </svg>
610
+ <p>Having determined that Angular.js is overkill for my <a href="http://intertwingly.net/blog/2014/12/19/Weblog-Software-Rewrite-Underway">blog rewrite</a>, I started looking more closely at <a href="http://facebook.github.io/react/">React</a>.  It occurred to me that I could do better than <a href="http://facebook.github.io/jsx/">JSX</a>, so I wrote a <a href="https://github.com/rubys/ruby2js">Ruby2JS</a> filter.  Compare for yourself.  Excerpt from the <a href="http://facebook.github.io/react/docs/tutorial.html">React tutorial</a>:</p>
611
+ <pre class="code">var CommentList = React.createClass({
612
+ render: function() {
613
+ var commentNodes = this.props.data.map(function (comment) {
614
+ return (
615
+ &lt;Comment author={comment.author}&gt;
616
+ {comment.text}
617
+ &lt;/Comment&gt;
618
+ );
619
+ });
620
+ return (
621
+ &lt;div className="commentList"&gt;
622
+ {commentNodes}
623
+ &lt;/div&gt;
624
+ );
625
+ }
626
+ });</pre>
627
+ <p>Equivalent using the Ruby2JS filter:</p>
628
+ <pre class="code">class CommentList &lt; React
629
+ def render
630
+ _div.commentList do
631
+ @@data.forEach do |comment|
632
+ _CommentBlock comment.text, author: comment.author
633
+ end
634
+ end
635
+ end
636
+ end</pre>
637
+ <p>Note: I renamed the <code>Comment</code> class to <code>CommentBlock</code> to avoid a conflict with the existing <a href="https://developer.mozilla.org/en-US/docs/Web/API/Comment">Comment</a> API.  I wouldn’t have thought that would be necessary, but things didn’t work until I made this change.</p>
638
+ <p><a href="https://github.com/rubys/ruby2js/blob/master/demo/react-tutorial.rb">Full source</a> for the tutorial reimplemented in Ruby is available.</p></div></content>
639
+ <updated>2015-01-22T17:54:56-08:00</updated>
640
+ </entry>
641
+
642
+ <entry>
643
+ <id>tag:intertwingly.net,2004:3345</id>
644
+ <link href="/blog/2015/01/17/RFC-3986bis"/>
645
+ <link rel="replies" href="3345.atom" thr:count="1" thr:updated="2017-05-13T05:51:06-07:00"/>
646
+ <title>RFC 3986bis</title>
647
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">URL parsers consume URLs and generate URIs.  Such URIs are not <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a> complaint.  I’d like to fix that.</div></summary>
648
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="132" height="76" viewBox="0 0 132 76">
649
+ <path d='M57,29c0-9,18-12,17-2c0,7-12,10-12,18v5h8v-3c0-7,13-9,13-21c-1-16-34-16-34,3zM62,53h8v8h-8z' fill='#371'></path>
650
+ <circle cy='38' stroke='#371' fill='none' r='33' stroke-width='10' cx='66'></circle>
651
+ <path d='M45,17l9,9l-9,10l-9-10zM67,17l9,9l-9,10l-10-10zM88,17l9,9l-9,10l-9-10zM14,28l9,9l-9,9l-9-9zM35,28l9,9l-9,9l-9-9zM56,28l9,9l-9,9l-9-9zM77,28l9,9l-9,9l-9-9zM98,28l9,9l-9,9l-9-9zM119,28l10,9l-10,9l-9-9zM45,39l9,9l-9,9l-9-9zM67,39l9,9l-9,9l-10-9zM88,39l9,9l-9,9l-9-9z' fill='#bdbdc5'></path>
652
+ <path d='M44,13l9,31l9-31h25v3l-10,16c23,7,2,52-16,21l6-2c11,21,24-16,2-14v-3l9-15h-11l-13,44h-1l-10-31l-9,31h-1l-15-50h7l9,31l6-21l-3-10z' fill='#005A9C'></path>
653
+ <path stroke='#000' d='M5,36h20l10,10l10-10l11,10l21-21l11,11l10-11l12,11h19v3h-20l-11-11l-10,11l-11-11l-21,21l-11-10l-10,10l-11-10h-19z' fill='#ffd652' stroke-width='0.5'></path>
654
+ <path d='M88,49c11,24,22,11,26,5l-1-5c-12,20-24,2-25,0M109,21c-8-16-26,0-16,23c-4-23,12-29,17-16l4-8l-1-6'></path>
655
+ <path d='M2,35h5v5h-5zM127,35h5v5h-5z'></path>
656
+ <path d='M57,29c0-9,18-12,17-2c0,7-12,10-12,18v5h8v-3c0-7,13-9,13-21c-1-16-34-16-34,3zM62,53h8v8h-8z' fill='#371'></path>
657
+ </svg>
658
+ <p>TL;DR: URL parsers consume URLs and generate URIs.  Such URIs are not <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a> complaint.  I’d like to fix that.</p>
659
+ <p> - - -</p>
660
+ <p>Let’s talk a bit about nomenclature.</p>
661
+ <p>On the web, particularly in places like values of attributes named <a href="http://www.w3.org/TR/html5/links.html#links-created-by-a-and-area-elements">href</a>, there are things that people have, at various times, attempted to call <a href="http://en.wikipedia.org/wiki/Uniform_resource_locator">web addresses</a> or <a href="http://www.ietf.org/rfc/rfc3987.txt">IRIs</a>.  Neither term has stuck.  In common uses these are called <a href="https://url.spec.whatwg.org/">URLs</a>.</p>
662
+ <p>In between the markup and servers, there are user agents.  One such user agent is a browser.  Browsers don’t passively send URLs along, they reject some outright, and transform others.  There should be a name for the set of outputs of the various cleanups that browsers perform.</p>
663
+ <p>Since browsers are programmable, you can directly observe this transformation.  The WHATWG URL specification defines an <a href="https://url.spec.whatwg.org/#api">API</a> which has already been implemented by Firefox and Chrome, and is being evaluated by Microsoft and Apple.  Create a JavaScript console and enter the following:</p>
664
+ <pre class="code">new URL("hTtP:/EXamPLe.COM/").href</pre>
665
+ <p>The output you will see is:</p>
666
+ <pre class="code">"http://example.com/"</pre>
667
+ <p>The output is clearly much cleaner and more consistent than the input.  In fact, in this case the output is RFC 3986 compliant.</p>
668
+ <p>Unfortunately, in the general case, this isn’t true.  Browsers (and more generally: other libraries like the ones found in pretty much every modern programming language) can produce things that aren’t RFC 3986 compliant.</p>
669
+ <p>I’m <a href="https://url.spec.whatwg.org/interop/test-results/">looking</a> at every browser and every library I can.  I’m specifically looking for differences.  In some cases, I’m pointing out where such outputs are clearly wrong and need to be fixed.</p>
670
+ <p>In other cases, the output may not be RFC 3986 compliant, but actually are useful and actually work.  What this means in practice is that the set of things that consumers need to be able to correctly process is not defined by RFC 3986 but by these tools.</p>
671
+ <p>People can learn this the hard way by starting out to implement RFC 3986 and then finding that they need to reverse engineer other tools.  We can do better.  We can set out to update RFC 3986 or otherwise document what the actual set of inputs that can be expected to interoperably process is.</p>
672
+ <p>In general, I have found that it isn’t difficult to talk about places where RFC 3986 can be tightened up.  Where there has been push-back is exploring any notion of loosening the definition.  The reaction generally is expressed along the lines of “doing so would break things”.</p>
673
+ <p>I can see how some see such a position as reasonable.  I don’t, and I’ll tell you why.  What is effectively being said is that documenting how things actually work will break things, which is clearly untrue.</p>
674
+ <p>What such an effort will do is not break things, but uncover uncomfortable truths.  To build upon an <a href="http://www.ietf.org/mail-archive/web/apps-discuss/current/msg13827.html">example</a> from Dave Cridland, one such uncomfortable truth may be that the sets of things that everybody except LDAP schemas can handle is different than the sets of things LDAP schemas can handle.</p>
675
+ <p>There are three ways to handle that.  One would be to change everybody to conform to what LDAP can handle.  One would be to change LDAP.  And one would be to document clearly that the set of things LDAP can handle and the set of things that everybody else expects to be handled are separate sets.  Largely overlapping, yes, but not identical sets.</p>
676
+ <p>While documenting three sets (the set of things Chrome and other browser supports, the set of things HTTP and other protocols support, and the set of things LDAP supports) would not be my first choice, but it may be the only option available given the constraints.</p>
677
+ <p>If you look at those three sets, ideally each would be a proper subset of these that precede it.  That’s not currently the case at the moment, but as I mentioned proposals made with clear rationale provided to tighten up RFC 3986 don’t seem to be getting much push-back.</p>
678
+ <p>What we need then it three names.  URIs seem to be the obvious choice for name of the set of “things LDAP schemas support”.  For better or worse, URLs seem to be the name that has stuck for the first set.</p>
679
+ <p>At this point, a number of people seeing an opening suggest IRIs as the name for the set in the middle.  Um, no.  Except for fragments, this set is 100% pure ASCII.  The name for what IRIs attempted to define is URLs.</p>
680
+ <p>So this means that we need to define a new name.  That’s not so bad, is it?  It could be worse, at least we don’t have to define a <a href="http://martinfowler.com/bliki/TwoHardThings.html">cache invalidation</a> strategy.</p></div></content>
681
+ <updated>2015-01-17T10:55:26-08:00</updated>
682
+ </entry>
683
+
684
+ <entry>
685
+ <id>tag:intertwingly.net,2004:3344</id>
686
+ <link href="/blog/2015/01/11/URL-Work-Status"/>
687
+ <link rel="replies" href="3344.atom" thr:count="17" thr:updated="2017-05-25T19:31:54-07:00"/>
688
+ <title>URL Work Status</title>
689
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>I have <a
690
+ href="https://url.spec.whatwg.org/interop/test-results/">test results</a> that
691
+ show that there is much work to be done.</p> <p>The most likely path forward
692
+ at this point is to get representatives from browser vendors into a room and
693
+ go through these results and make recommendations. This likely will happen in
694
+ the spring, and in the SF Bay Area. With that in place, I can work with
695
+ authors of libraries in popular programming languages to produce
696
+ web-compatible versions. This work will take the form of bug reports,
697
+ patches, or — when required — authoring new libraries.</p></div></summary>
698
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="132" height="76" viewBox="0 0 132 76">
699
+ <path d='M57,29c0-9,18-12,17-2c0,7-12,10-12,18v5h8v-3c0-7,13-9,13-21c-1-16-34-16-34,3zM62,53h8v8h-8z' fill='#371'></path>
700
+ <circle cy='38' stroke='#371' fill='none' r='33' stroke-width='10' cx='66'></circle>
701
+ <path d='M45,17l9,9l-9,10l-9-10zM67,17l9,9l-9,10l-10-10zM88,17l9,9l-9,10l-9-10zM14,28l9,9l-9,9l-9-9zM35,28l9,9l-9,9l-9-9zM56,28l9,9l-9,9l-9-9zM77,28l9,9l-9,9l-9-9zM98,28l9,9l-9,9l-9-9zM119,28l10,9l-10,9l-9-9zM45,39l9,9l-9,9l-9-9zM67,39l9,9l-9,9l-10-9zM88,39l9,9l-9,9l-9-9z' fill='#bdbdc5'></path>
702
+ <path d='M44,13l9,31l9-31h25v3l-10,16c23,7,2,52-16,21l6-2c11,21,24-16,2-14v-3l9-15h-11l-13,44h-1l-10-31l-9,31h-1l-15-50h7l9,31l6-21l-3-10z' fill='#005A9C'></path>
703
+ <path stroke='#000' d='M5,36h20l10,10l10-10l11,10l21-21l11,11l10-11l12,11h19v3h-20l-11-11l-10,11l-11-11l-21,21l-11-10l-10,10l-11-10h-19z' fill='#ffd652' stroke-width='0.5'></path>
704
+ <path d='M88,49c11,24,22,11,26,5l-1-5c-12,20-24,2-25,0M109,21c-8-16-26,0-16,23c-4-23,12-29,17-16l4-8l-1-6'></path>
705
+ <path d='M2,35h5v5h-5zM127,35h5v5h-5z'></path>
706
+ <path d='M57,29c0-9,18-12,17-2c0,7-12,10-12,18v5h8v-3c0-7,13-9,13-21c-1-16-34-16-34,3zM62,53h8v8h-8z' fill='#371'></path>
707
+ </svg>
708
+ <p>I have <a href="https://url.spec.whatwg.org/interop/test-results/">test
709
+ results</a> that show that there is much work to be done.</p>
710
+ <p>The most likely path forward at this point is to get representatives from
711
+ browser vendors into a room and go through these results and make
712
+ recommendations. This likely will happen in the spring, and in the SF Bay
713
+ Area. With that in place, I can work with authors of libraries in popular
714
+ programming languages to produce web-compatible versions. This work will take
715
+ the form of bug reports, patches, or — when required — authoring new
716
+ libraries.</p>
717
+ <p>Status by venue:</p>
718
+ <dl>
719
+ <dt><b>WHATWG</b></dt>
720
+ <dd><p>At the WHATWG, I’m limited only by my own ability to do the work
721
+ required. My biggest complaint remains that that the barrier to entry to
722
+ participate is too high. This. however, is something entirely under my
723
+ control to fix for the specifications I’m working on. I’m hopeful that
724
+ leading by example will cause others in the WHATWG to do likewise.</p></dd>
725
+
726
+ <dt><b>WebPlatform</b></dt>
727
+ <dd><p>I’ve had <a href="https://github.com/webspecs/url/issues">some success</a>,
728
+ but virtually all of this is attributable to GitHub, not WebPlatform. At the
729
+ moment, technical issues prevent me from updating the spec there. These
730
+ issues started on December 24th and were promptly reported. If this
731
+ continues, I’ll push the webspecs develop branch to a whatwg develop branch
732
+ and <a href="https://github.com/IQAndreas/github-issues-import">migrate the
733
+ issues</a>.</p></dd>
734
+
735
+ <dt><b>W3C</b></dt>
736
+ <dd><p>There has been no demonstrable progress in the WebApps WG. The <a
737
+ href="http://www.w3.org/2001/tag/">TAG</a> seems generally supportive. I
738
+ briefed the <a href="http://www.w3.org/2002/ab/">AB</a>, but nothing has come
739
+ of that. Same is <a
740
+ href="http://www.w3.org/community/w3process/track/issues/150">true</a> for the
741
+ process CG. I’m willing to try proposing a <a
742
+ href="http://rawgit.com/webspecs/url/develop/docs/url-charter.html">new
743
+ working group</a>. Failing this, I believe that I have all the evidence I
744
+ need to convince the W3C Director that <a
745
+ href="http://www.w3.org/2013/09/normative-references">normative references</a>
746
+ to the Living Standard are the only viable alternative. As Sherlock Holmes
747
+ was known to say: <em>when you have eliminated the impossible, whatever
748
+ remains, however improbable, must be the truth</em>.</p></dd>
749
+
750
+ <dt><b>IETF</b></dt>
751
+ <dd><p>I’ve <a
752
+ href="https://lists.w3.org/Archives/Public/public-whatwg-archive/2014Nov/0000.html">met
753
+ with</a> Area Directors. I’ve participated on the <a
754
+ href="http://www.ietf.org/mail-archive/web/apps-discuss/current/maillist.html">apps-discuss
755
+ mailing list</a>. With the help of <a href="http://larry.masinter.net/">Larry
756
+ Masinter</a>, I’ve produced and published a <a
757
+ href="http://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https%3A%2F%2Fraw.githubusercontent.com%2Fwebspecs%2Furl%2Fdevelop%2Fdocs%2Furl-problem-statement.xml&amp;modeAsFormat=html%2Fascii">problem
758
+ statement</a>. Sadly, this seems like a clear case of <em>you can lead a
759
+ horse to water, but you can’t make it drink</em>. Should this change, I have
760
+ until <a href="http://www.ietf.org/meeting/important-dates-2015.html">February
761
+ 5th</a> to propose a BOF.</p></dd>
762
+ </dl>
763
+
764
+ <p>More details and links are available in the
765
+ <a href="https://github.com/webspecs/url#the-url-standard">README</a>.</p></div></content>
766
+ <updated>2015-01-11T06:46:06-08:00</updated>
767
+ </entry>
768
+
769
+ <entry>
770
+ <id>tag:intertwingly.net,2004:3343</id>
771
+ <link href="/blog/2015/01/08/Ununzippable-Modern-IE"/>
772
+ <link rel="replies" href="3343.atom" thr:count="7" thr:updated="2017-05-12T22:09:12-07:00"/>
773
+ <title>Ununzippable Modern.IE</title>
774
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">I’ve downloaded the multi-part zip archive for IE11 on Win10 for VirtualBox on OS/X from <a href="https://www.modern.ie/">modern.ie</a>.  I’ve downloaded the single-file archive on both OS/X and Linux.  I’ve verified the md5 signatures for each.  Yet each time, when I try to unzip the result, I fail.</div></summary>
775
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
776
+ <path d="M57,11c40-22,42-2,35,12c8-27-15-20-30-11z" fill="#47b"></path>
777
+ <path d="M36,56h56c4-60-83-60-86-6c13-16,26-26,36-30l-29,53c20,23,64,26,79-12h-30c0,14-26,12-25-5zM37,43c0-17,26-17,26,0zM39,89c-10,7-42,15-26-16l29-52c-15,6-36,40-37,48c-12,35,14,37,37,20" fill="#47b"></path>
778
+ </svg>
779
+ <p>I’ve downloaded the multi-part zip archive for IE11 on Win10 for VirtualBox on OS/X from <a href="https://www.modern.ie/">modern.ie</a>.  I’ve downloaded the single-file archive on both OS/X and Linux.  I’ve verified the md5 signatures for each.  Yet each time, when I try to unzip the result, I get the following:</p>
780
+ <pre class="code">$ unzip IE11.Win10.For.LinuxVirtualBox.zip
781
+ Archive: IE11.Win10.For.LinuxVirtualBox.zip
782
+ warning [IE11.Win10.For.LinuxVirtualBox.zip]: 4294967296 extra bytes at beginning or within zipfile
783
+ (attempting to process anyway)
784
+ file #1: bad zipfile offset (local header sig): 4294967296
785
+ (attempting to re-compensate)
786
+ inflating: IE11 - Win10.ova
787
+ error: invalid compressed data to inflate</pre>
788
+ <p>I’ve even tried <a href="http://serverfault.com/a/434537">jar xf</a> with no luck:</p>
789
+ <pre class="code">$ jar xf IE11.Win10.For.LinuxVirtualBox.zip
790
+ java.util.zip.ZipException: invalid entry size (expected 5632888297048912 but got 4801961472 bytes)
791
+ at java.util.zip.ZipInputStream.readEnd(ZipInputStream.java:403)
792
+ at java.util.zip.ZipInputStream.read(ZipInputStream.java:195)
793
+ at java.util.zip.ZipInputStream.closeEntry(ZipInputStream.java:139)
794
+ at sun.tools.jar.Main.extractFile(Main.java:961)
795
+ at sun.tools.jar.Main.extract(Main.java:877)
796
+ at sun.tools.jar.Main.run(Main.java:263)
797
+ at sun.tools.jar.Main.main(Main.java:1177)</pre>
798
+ <p>This shows signs of <a href="http://googology.wikia.com/wiki/4294967296">integer overflow</a>, so it seems likely that the problem is client side.  Even with that said, choosing to make a this content available in a format for which there isn’t working client libraries available to unpack it isn’t helpful.</p>
799
+ <p>I’m submitting this link as <a href="https://www.modern.ie/en-us/feedback">feedback</a>.</p></div></content>
800
+ <updated>2015-01-08T03:55:41-08:00</updated>
801
+ </entry>
802
+
803
+ <entry>
804
+ <id>tag:intertwingly.net,2004:3342</id>
805
+ <link href="/blog/2015/01/06/New-PhantomJS-and-Capybara-fan"/>
806
+ <link rel="replies" href="3342.atom" thr:count="1" thr:updated="2015-02-24T19:58:33-08:00"/>
807
+ <title>New PhantomJS and Capybara fan</title>
808
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="96" height="104" viewBox="0 0 96 104">
809
+ <path d='M4,88c4,3,10,1,16,3c4,1,6,11,11,9c5-1,14-6,20-4c6,1,13,12,17,7c4-5,6-16,16-15c10,1,12,1,11-12c-1-7,5-13-3-18c-13-9-34-3-46,5c-14,10-47,20-42,25' fill='#000' fill-opacity='0.23'></path>
810
+ <path d='M82,43c0,22,9,27,9,37c0,5-12,1-17,4c-4,3-2,7-9,9c-4,2-10-6-17-6c-5,0-14,7-19,4c-4-2-4-9-10-10c-6,0-19,7-19,1c0-7,8-14,8-39c0-23,17-43,37-42c21,0,37,20,37,44' fill='#ccc' fill-opacity='0.63' stroke='#000'></path>
811
+ <path d='M33,22c-5,0-9,4-9,10c0,6,4,10,9,10c4,0,7-2,9-6c1,4,4,6,8,6c5,0,9-4,9-10c0-6-4-10-9-10c-4,0-7,2-8,6c-2-4-5-6-9-6' fill='#fff' stroke='#000'></path>
812
+ <circle cx="36" cy="34" r="4"></circle>
813
+ <circle cx="48" cy="34" r="4"></circle>
814
+ <path d='M69,15c2,0,9,9,10,19l2,23c1,6,10,22,6,21c-8,0-13-13-12-33c3-21-9-29-6-30M73,82c-2,2-6,11-7,7c-2-4-1-11-2-22c-1-8-5-18-2-16c3,1,5,5,7,13c3,7,6,16,4,18M45,85c-2,2-6,4-9,3c-3,0-3-10-2-17c1-6,4-16,6-17c2-2,0,6,2,18c2,8,5,11,3,13
815
+ M20,79c-2,0-5,0-7,1c-3,1,0-4,4-11c3-6,4-12,5-14c2-2,0,6-1,13c0,7,1,11-1,11' fill-opacity='0.12'></path>
816
+ </svg>
817
+ <p>While I’m clearly late to the party, I’ve already become a huge fan of <a href="http://jnicklas.github.io/capybara/">capybara</a> and <a href="http://phantomjs.org/">phantomjs</a>.  I’m now using both with my <a href="http://intertwingly.net/blog/2014/12/19/Weblog-Software-Rewrite-Underway">previously mentioned</a> <a href="https://github.com/rubys/wicker">blogging software</a> rewrite.</p>
818
+ <p>My original intent was to aggressively prune unnecessary function with the intent of producing a more maintainable result, but with the ability to have automated acceptance tests, this is now less of a concern.</p></div></content>
819
+ <updated>2015-01-06T11:47:40-08:00</updated>
820
+ </entry>
821
+
822
+ <entry>
823
+ <id>tag:intertwingly.net,2004:3341</id>
824
+ <link href="/blog/2015/01/05/Apple-Apostasy"/>
825
+ <link rel="replies" href="3341.atom" thr:count="8" thr:updated="2015-01-06T16:33:30-08:00"/>
826
+ <title>Apple Apostasy</title>
827
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="90" height="100" viewBox="0 0 90 100">
828
+ <path d='M62,0c2,10-9,24-20,24c-3-14,9-22,20-24M5,36c5-8,13-12,21-12c7,0,12,4,19,4c6,0,10-4,19-4c6,0,14,3,19,10c-16,4-15,35,3,39c-7,17-18,27-24,27c-7,0-8-5-17-5c-9,0-11,5-17,5c-7-1-13-7-17-13c-9-10-15-40-6-51' fill='#AAA'></path>
829
+ </svg>
830
+ <p>Looks like <a href="http://wozniak.ca/why-i-quit-os-x">Why I quit OS X</a> struck a nerve — it is currently down (see <a href="http://web.archive.org/web/20150105063342/http://wozniak.ca/why-i-quit-os-x">web archive</a>).  Also good: <a href="http://www.marco.org/2015/01/04/apple-lost-functional-high-ground">Apple has lost the functional high ground</a>.</p>
831
+ <p>I particularly like the comment that <em>“It just works” was never completely true</em>.  My experience is that when working with open source codebases, doing so on an Linux operating system comes much closer to “It just works” than doing so on any other.</p></div></content>
832
+ <updated>2015-01-05T12:09:46-08:00</updated>
833
+ </entry>
834
+
835
+ <entry>
836
+ <id>tag:intertwingly.net,2004:3340</id>
837
+ <link href="/blog/2015/01/03/Rack-broke-Sinatra"/>
838
+ <link rel="replies" href="3340.atom" thr:count="4" thr:updated="2017-05-25T22:32:58-07:00"/>
839
+ <title>Rack broke Sinatra</title>
840
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="90" height="111" viewBox="0 0 90 111">
841
+ <g stroke-linejoin="bevel" stroke-linecap="square" fill="none" stroke="#000">
842
+ <path d="M6,15l30-10l49,11v82v-82l-24,8v83v-83l-55-9v83l56,8l23-8" stroke-width="4"></path>
843
+ <path d="M6,98l27-9v-13l-26-4l27-8v-20l-27-5l28-8v-11v11l49,10l-24,8l-26-5v20l50,8l-24,9l-27-5v13l51,8" stroke-width="2"></path>
844
+ </g>
845
+ </svg>
846
+ <p>Not rack’s fault, but Sinatra hasn’t released in a while.  Problem has been known since <a href="https://github.com/sinatra/sinatra/pull/907">July</a>, and a fix was merged into master in <a href="https://github.com/sinatra/sinatra/commit/a43ba2c65a79bf58adc1291b0079ea889310e072">August</a>.  One <a href="https://github.com/honeybadger-io/honeybadger-ruby/commit/0e1d652992160fcf1bb3f2e53fbfafdad4d9047d">possible workaround</a> has been posted.  An alternate workaround:</p>
847
+ <pre class="code">module Rack
848
+ class ShowExceptions
849
+ alias_method :old_pretty, :pretty
850
+ def pretty(*args)
851
+ result = old_pretty(*args)
852
+ def result.join; self; end
853
+ def result.each(&amp;block); block.call(self); end
854
+ result
855
+ end
856
+ end
857
+ end</pre></div></content>
858
+ <updated>2015-01-03T17:31:33-08:00</updated>
859
+ </entry>
860
+
861
+ <entry>
862
+ <id>tag:intertwingly.net,2004:3339</id>
863
+ <link href="/blog/2014/12/19/Weblog-Software-Rewrite-Underway"/>
864
+ <link rel="replies" href="3339.atom" thr:count="3" thr:updated="2017-05-23T01:57:34-07:00"/>
865
+ <title>Weblog Software Rewrite Underway</title>
866
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>I’ve clearly been neglecting my little spot on the web.</p>
867
+ <p>It has gotten so bad that <a href="https://twitter.com/BrendanEich/status/544975709404282881">Brendan Eich</a> had to link to a web archive copy of a page of mine.  I must say, however, that it is very ironic and amusing that it is was <a href="http://www.intertwingly.net/blog/2005/03/15/Dont-Panic">that particular page</a>.  General outline of my current approach:</p></div></summary>
868
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
869
+ <defs xmlns:xlink="http://www.w3.org/1999/xlink">
870
+ <radialGradient id="s1" fx=".4" fy=".2" r=".7">
871
+ <stop stop-color="#FE8"></stop><stop stop-color="#D70" offset="1"></stop>
872
+ </radialGradient>
873
+ <radialGradient id="s2" fx=".8" fy=".5" xlink:href="#s1"></radialGradient>
874
+ <radialGradient id="s3" fx=".5" fy=".9" xlink:href="#s1"></radialGradient>
875
+ <radialGradient id="s4" fx=".1" fy=".5" xlink:href="#s1"></radialGradient>
876
+ </defs>
877
+ <g stroke="#940">
878
+ <path d="M73,29c-37-40-62-24-52,4l6-7c-8-16,7-26,42,9z" fill="url(#s1)"></path>
879
+ <path d="M47,8c33-16,48,21,9,47l-6-5c38-27,20-44,5-37z" fill="url(#s2)"></path>
880
+ <path d="M77,32c22,30,10,57-39,51l-1-8c3,3,67,5,36-36z" fill="url(#s3)"></path>
881
+ <path d="M58,84c-4,20-38-4-8-24l-6-5c-36,43,15,56,23,27z" fill="url(#s4)"></path>
882
+ <path d="M40,14c-40,37-37,52-9,68l1-8c-16-13-29-21,16-56z" fill="url(#s1)"></path>
883
+ <path d="M31,33c19,23,20,7,35,41l-9,1.7c-4-19-8-14-31-37z" fill="url(#s2)"></path>
884
+ </g>
885
+ </svg>
886
+ <p>I’ve clearly been neglecting my little spot on the web.</p>
887
+ <p>It has gotten so bad that <a href="https://twitter.com/BrendanEich/status/544975709404282881">Brendan Eich</a> had to link to a web archive copy of a page of mine.  I must say, however, that it is very ironic and amusing that it is was <a href="http://www.intertwingly.net/blog/2005/03/15/Dont-Panic">that particular page</a>.  The problem turned out not to be a software problem, but rather a (presumably inadvertent) DOS attack on <a href="http://feedvalidator.org/about.html">feedvalidator.org</a>, causing CGI processes to fail.  Blocking the IP address in question caused the problem to clear up.</p>
888
+ <p>General outline of my current approach:</p>
889
+ <ul>
890
+ <li>My interface to my weblog will no longer be Python/CGI application on a hosted server.  Instead it will be a Ruby/Sinatra application on my private home server where keeping things up to date is much easier for me.  That application will produce static HTML, CSS, StyleSheet, and a single feed, all of which will be <a href="http://linux.die.net/man/1/rsync">rsync</a>'ed to the public server.</li>
891
+ <li>The only services exposed will be search and comments.  Comments initially be disabled, and when they return they will likely be moderated, though I may make the moderation queue publicly visible.</li>
892
+ <li>My current focus is a software update.  The overall look and feel will (at least initially) remain the same.  </li>
893
+ <li>The pages produced will be HTML5, though all pages may not always pass <a href="http://html5doctor.com/html5-check-it-before-you-wreck-it-with-miketm-smith/">validation</a>.  Mike is 100% correct: <em>different people can make different judgment calls</em>.  In particular, I continue to find that explicitly quoting all attributes and explicitly closing all elements both reduces authoring errors and enables a wider variety of user agents to parse the pages correctly.</li>
894
+ <li>I’ll likely drop many features that were popular at one time, but no longer appear to be.  An example of this: <a href="http://openid.net/">OpenID</a>.</li>
895
+ </ul></div></content>
896
+ <updated>2014-12-19T06:56:29-08:00</updated>
897
+ </entry>
898
+
899
+ <entry>
900
+ <id>tag:intertwingly.net,2004:3338</id>
901
+ <link href="/blog/2014/11/20/WHATWG-W3C-Collaboration"/>
902
+ <link rel="replies" href="3338.atom" thr:count="6" thr:updated="2017-05-25T23:59:27-07:00"/>
903
+ <title>WHATWG/W3C Collaboration</title>
904
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">I’ve been having fun working on the <a href="https://url.spec.whatwg.org/">URL Living Standard</a>. All good things must come to an end. Now it is time to spell out a path forward.</div></summary>
905
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns='http://www.w3.org/2000/svg' width="132" height="76" viewBox="0 0 132 76">
906
+ <path d='M57,29c0-9,18-12,17-2c0,7-12,10-12,18v5h8v-3c0-7,13-9,13-21c-1-16-34-16-34,3zM62,53h8v8h-8z' fill='#371'></path>
907
+ <circle cy='38' stroke='#371' fill='none' r='33' stroke-width='10' cx='66'></circle>
908
+ <path d='M45,17l9,9l-9,10l-9-10zM67,17l9,9l-9,10l-10-10zM88,17l9,9l-9,10l-9-10zM14,28l9,9l-9,9l-9-9zM35,28l9,9l-9,9l-9-9zM56,28l9,9l-9,9l-9-9zM77,28l9,9l-9,9l-9-9zM98,28l9,9l-9,9l-9-9zM119,28l10,9l-10,9l-9-9zM45,39l9,9l-9,9l-9-9zM67,39l9,9l-9,9l-10-9zM88,39l9,9l-9,9l-9-9z' fill='#bdbdc5'></path>
909
+ <path d='M44,13l9,31l9-31h25v3l-10,16c23,7,2,52-16,21l6-2c11,21,24-16,2-14v-3l9-15h-11l-13,44h-1l-10-31l-9,31h-1l-15-50h7l9,31l6-21l-3-10z' fill='#005A9C'></path>
910
+ <path stroke='#000' d='M5,36h20l10,10l10-10l11,10l21-21l11,11l10-11l12,11h19v3h-20l-11-11l-10,11l-11-11l-21,21l-11-10l-10,10l-11-10h-19z' fill='#ffd652' stroke-width='0.5'></path>
911
+ <path d='M88,49c11,24,22,11,26,5l-1-5c-12,20-24,2-25,0M109,21c-8-16-26,0-16,23c-4-23,12-29,17-16l4-8l-1-6'></path>
912
+ <path d='M2,35h5v5h-5zM127,35h5v5h-5z'></path>
913
+ <path d='M57,29c0-9,18-12,17-2c0,7-12,10-12,18v5h8v-3c0-7,13-9,13-21c-1-16-34-16-34,3zM62,53h8v8h-8z' fill='#371'></path>
914
+ </svg>
915
+ <p>I’ve been having fun working on the <a
916
+ href="https://url.spec.whatwg.org/">URL Living Standard</a>. The first change
917
+ I landed was to convert the spec from <a
918
+ href="https://wiki.whatwg.org/wiki/Anolis">Anolis</a> to <a
919
+ href="https://github.com/tabatkins/bikeshed#readme">Bikeshed</a>. Here’s the
920
+ <a href="https://rawgit.com/whatwg/url/8be4726f53/url.html">before</a> and
921
+ after <a href="https://rawgit.com/whatwg/url/bd3f0ce38f/url.html">after</a>.
922
+ And just for fun, here is <a
923
+ href="https://rawgit.com/whatwg/url/232157a284/url.html">the beginning on
924
+ 2014</a> and <a
925
+ href="https://rawgit.com/whatwg/url/bdaff0591b/url.html">beginning of 2013</a>.
926
+ The point being that arbitrary snapshots of living standards do exist.</p>
927
+
928
+ <p>Along the way, I’ve been named by my employer’s AC member to be a member of
929
+ the <a href="http://www.w3.org/2008/webapps/">W3C WebApps Working Group</a>,
930
+ and invited to become a member of the <a href="https://whatwg.org/">WHATWG</a>
931
+ organization on GitHub.
932
+ I’ve been named as co-editor of the spec in both organizations, and at that
933
+ point the fun abruptly stopped. Apparently, the larger political issues that I
934
+ had successfully avoided in the past moved front and center.</p>
935
+
936
+ <p>Here’s what I <a
937
+ href="http://intertwingly.net/blog/2014/09/16/The-URL-Mess">said in
938
+ September</a>:</p>
939
+
940
+ <blockquote>
941
+ <p>While I am optimistic that at some point in the future the W3C will
942
+ feel comfortable referencing stable and consensus driven specifications
943
+ produced by the WHATWG, it is likely that some changes will be required to
944
+ one or both organizations for this to occur; meanwhile I encourage the W3C
945
+ to continue on the path of standardizing a snapshot version of the WHATWG
946
+ URL specification, and for HTML5 to reference the W3C version of the
947
+ specification.</p>
948
+ </blockquote>
949
+
950
+ <p>Now it is time for me to spell out how I see that happening.</p>
951
+
952
+ <p>I’ll start out by saying that I continue to want the WebApps WG to follow
953
+ through on its <a
954
+ href="http://www.w3.org/2014/06/webapps-charter.html#deliverables">charter
955
+ obligation</a> to continue to publish updates to the <a
956
+ href="http://www.w3.org/TR/url/">URL Working Draft</a>. And once updates
957
+ resume, I want to work on making doing so entirely unnecessary. While this may
958
+ sound puzzling, there is a method to my madness. I want to establish an
959
+ environment where an open discussion of this matter can be held without anybody
960
+ feeling that there are options that are closed to them or that there is a gun
961
+ to their head.</p>
962
+
963
+ <p>Next I’ll state an observable fact: there exists people who value the output
964
+ of the <a href="http://www.w3.org/2014/Process-20140801/">W3C process</a>. The
965
+ fact that there are people who don’t doesn’t make the first set of people go
966
+ away or become any less important. Note that I said the output of the W3C
967
+ process. People who value that don’t necessarily (or even generally) want to
968
+ observe or participate in the making of the sausage.</p>
969
+
970
+ <p>What they value instead is <a
971
+ href="http://lists.w3.org/Archives/Public/public-html-admin/2014Nov/0000.html">regular
972
+ releases and making the bleeding edge publicly available</a>. And for
973
+ releases, what they care most about are the items that are covered during a
974
+ W3C Transition (<a
975
+ href="http://www.w3.org/html/wg/cr/html5/transition-request.html">example</a>).
976
+ In particular, they are interested in evidence of wide review, evidence that
977
+ issues have been addressed, evidence that there are implementations, and the
978
+ IPR commitments that are captured along the way.</p>
979
+
980
+ <p>Some have (and do) argue that these needs can be met in other ways. Not
981
+ everybody is convinced of this. I’m not convinced. In particular, the
982
+ existence of a bugzilla database with numerous bugs closed as WORKS4ME
983
+ without explanation doesn’t satisfy me.</p>
984
+
985
+ <p>To date, those needs have intentionally not been met by the WHATWG. And
986
+ an uneasy arrangement has been created where specs have been republished at
987
+ the W3C with additional editors listed, in many cases in name only. Those
988
+ copies were then shepherded through the W3C process. Many are not happy
989
+ with this process. I personally can live with it, but I’d rather not.</p>
990
+
991
+ <p>I said that this will require changes by one or both organizations. I
992
+ will now say that I expect this to require cooperation and changes by both.
993
+ I’ll start by describing the changes I feel are needed by the WHATWG, of
994
+ which there are three.</p>
995
+
996
+ <ol>
997
+ <li>
998
+ <p>Agree to the production of planned snapshots. And by that I mean
999
+ byte-for-byte copies. As a part of this that would mean the
1000
+ identification of "items at risk" at early stages of the process, and
1001
+ the potential removal of these items later in the process. These
1002
+ snapshots will need to meet the needs of the W3C, primarily <a
1003
+ href="http://www.w3.org/2005/07/pubrules">pubrules</a>,
1004
+ and only linking to W3C approved references. Even though it should have
1005
+ to go without saying, <a
1006
+ href="https://whatwg.org/specs/url/2014-07-30/">apparently it needs to
1007
+ be said</a>: those specs need to be snark free. Finally I'll go further
1008
+ and suggest that those snapshots be hosted by the W3C, much in the way
1009
+ that the W3C hosts WHATWG's bugzilla database and mailing list
1010
+ archives.</p>
1011
+ </li>
1012
+
1013
+ <li>
1014
+ <p>Participation in the production of <a
1015
+ href="http://www.w3.org/2005/08/01-transitions.html#transreq">Transition
1016
+ Requests</a>. That would involve providing evidence of wide review and
1017
+ evidence that issues are addressed. It also could include, but doesn't
1018
+ necessary require, direct participation in the transition calls.
1019
+ </p>
1020
+ </li>
1021
+
1022
+ <li>
1023
+ <p>Understanding and internalizing the notion that the combination of an
1024
+ open license coupled with begin unwilling or unable to address a
1025
+ perceived need by others is a valid reason for a fork. Yes, I know that
1026
+ the W3C hasn't adopted an open license themselves, and I believe that is
1027
+ wrong too. But that doesn't change the fact that an open license plus
1028
+ an unmet need is sufficient justification for a fork.</p>
1029
+ </li>
1030
+ </ol>
1031
+
1032
+ <p>I’ll close my discussion on the WHATWG changes I envision with a statement
1033
+ that participation in the W3C process (to the extent described by #1 and #2
1034
+ above) is optional and will likely be done on a spec by spec basis. Editors of
1035
+ some WHATWG specs may not chose not to participate in this process, and that’s
1036
+ OK, I simply ask that those that don’t recognize the implications of this
1037
+ choice (specifically #3 above).</p>
1038
+
1039
+ <p>Responsibility for advancing specs for which the WHATWG editors
1040
+ voluntarily elect to participate in the process would fall to a sponsoring
1041
+ W3C Working Group. Starting to sponsor, ceasing to sponsor, and forking a
1042
+ spec would require explicit W3C Working Group decisions. As a general rule,
1043
+ Working Groups should only consider sponsoring focused, modular
1044
+ specifications.</p>
1045
+
1046
+ <p>Here’s what sponsoring would (and most importantly, would <em>not</em>)
1047
+ involve:</p>
1048
+
1049
+ <ol>
1050
+ <li>
1051
+ <p>
1052
+ No editing. As suggested above, snapshots produced by the WHATWG
1053
+ would be archived, but these archives would be byte-for-byte beyond
1054
+ the changes involved in archiving itself (example: updating stylesheet
1055
+ links to point to captured snapshots of stylesheets). The one
1056
+ possible exception to this would be in the updating of normative
1057
+ references, but this would only be done with the concurrence of the
1058
+ WHATWG editors.
1059
+ </p>
1060
+ </li>
1061
+
1062
+ <li>
1063
+ <p>Participation would be limited to the production of <a
1064
+ href="http://www.w3.org/2005/08/01-transitions.html#transreq">Transition
1065
+ Requests</a>. This would include providing evidence of <a
1066
+ href="http://www.w3.org/2014/Process-20140801/#wide-review">wide
1067
+ review</a>, evidence that issues are <a
1068
+ href="http://www.w3.org/2014/Process-20140801/#formal-address">formally
1069
+ addressed</a>, <a
1070
+ href="http://www.w3.org/2014/Process-20140801/#WGArchiveMinorityViews">recording
1071
+ and reporting of Formal Objections</a>, collecting patent <a
1072
+ href="http://www.w3.org/Consortium/Patent-Policy-20040205/#sec-Disclosure">disclosures</a>,
1073
+ etc.</p>
1074
+ </li>
1075
+ </ol>
1076
+
1077
+ <p>That’s it. Of course, the process will remain the same for documents that
1078
+ are copied and shepherded instead, but I see no reason that <a
1079
+ href="http://www.w3.org/2008/webapps/">WebApps WG</a> couldn't sponsor the
1080
+ WHATWG <a href="https://url.spec.whatwg.org/">URL standard</a> through this
1081
+ process, the <a href="http://www.w3.org/html/wg/">HTML WG</a> couldn't do the
1082
+ same for the <a href="https://dom.spec.whatwg.org/">DOM standard</a>, the <a
1083
+ href="http://www.w3.org/International/core/">I18N WG</a> couldn't do the same
1084
+ for the <a href="https://encoding.spec.whatwg.org/">Encoding standard</a>,
1085
+ etc.</p>
1086
+
1087
+ <p>While everybody may come into a sponsorship collaboration with the best
1088
+ intentions, we need to realize that things may not always go as planned.
1089
+ There may be disagreements. It has been known to happen. When such
1090
+ occurs:</p>
1091
+
1092
+ <ol>
1093
+ <li><p>Everyone involved should work very hard to resolve the dispute as
1094
+ the consequence of breakage is very bad all around.</p></li>
1095
+
1096
+ <li><p>If no agreement can be reached, the W3C Working Group will likely
1097
+ stop the sponsorship of the specific spec involved in the dispute.</p></li>
1098
+
1099
+ <li><p>If a Working Group stops sponsoring a spec, the Working Group could
1100
+ still fork that spec - but that would be a suboptimal solution for both W3C and
1101
+ WHATWG. It would also re-inflame the debates between organizations.</p></li>
1102
+
1103
+ <li><p>Nonetheless, since each organization has different criteria, we must
1104
+ recognize that this could happen; especially for large, broad, complex
1105
+ specs. Accordingly it makes sense for both organizations to continue the
1106
+ trend towards smaller and more modular specifications</p></li>
1107
+ </ol>
1108
+
1109
+ <p>I have no idea if others are willing to go along with this, but I hope
1110
+ that this concrete proposal helps anchor this discussion. I invite others
1111
+ that are inclined to do so to suggest revisions or to create proposals of
1112
+ their own. As an example, since the above describes an environment of
1113
+ collaboration and sharing of work, perhaps co-branding may be worth
1114
+ exploring?</p>
1115
+
1116
+ <p>This clearly will take time. As an editor of the URL specification, I’d
1117
+ like to propose that it be the first test of this proposal. In the
1118
+ meanwhile, I plan to spend my time coding.</p>
1119
+
1120
+ <p>For those that wish to dig further, a few links:</p>
1121
+
1122
+ <small><ul>
1123
+ <li>
1124
+ <a href="http://www.w3.org/blog/2014/10/decision-by-consensus-or-by-informed-editor-which-is-better/">http://www.w3.org/blog/2014/10/decision-by-consensus-or-by-informed-editor-which-is-better/</a>
1125
+ </li>
1126
+ <li>
1127
+ <a href="http://lists.w3.org/Archives/Public/www-archive/2014Nov/0023.html">http://lists.w3.org/Archives/Public/www-archive/2014Nov/0023.html</a>
1128
+ </li>
1129
+ <li>
1130
+ <a href="http://lists.w3.org/Archives/Public/public-webapps/2014OctDec/0437.html">http://lists.w3.org/Archives/Public/public-webapps/2014OctDec/0437.html</a>
1131
+ </li>
1132
+ <li>
1133
+ <a href="https://url.spec.whatwg.org/#acknowledgments">https://url.spec.whatwg.org/#acknowledgments</a>
1134
+ </li>
1135
+ <li>
1136
+ <a href="http://lists.w3.org/Archives/Public/public-webapps/2014JulSep/0492.html">http://lists.w3.org/Archives/Public/public-webapps/2014JulSep/0492.html</a>
1137
+ </li>
1138
+ <li>
1139
+ <a href="http://lists.w3.org/Archives/Public/public-whatwg-archive/2014Nov/0000.html">http://lists.w3.org/Archives/Public/public-whatwg-archive/2014Nov/0000.html</a>
1140
+ </li>
1141
+ <li>
1142
+ <a href="http://lists.w3.org/Archives/Public/public-webapps/2014OctDec/0315.html">http://lists.w3.org/Archives/Public/public-webapps/2014OctDec/0315.html</a>
1143
+ </li>
1144
+ <li>
1145
+ <a href="http://lists.w3.org/Archives/Public/public-html-admin/2014Nov/0036.html">http://lists.w3.org/Archives/Public/public-html-admin/2014Nov/0036.html</a>
1146
+ </li>
1147
+ <li>
1148
+ <a href="http://intertwingly.net/blog/2014/10/02/WHATWG-URL-vs-IETF-URI">http://intertwingly.net/blog/2014/10/02/WHATWG-URL-vs-IETF-URI</a>
1149
+ </li>
1150
+ </ul></small></div></content>
1151
+ <updated>2014-11-20T08:55:43-08:00</updated>
1152
+ </entry>
1153
+
1154
+ <entry>
1155
+ <id>tag:intertwingly.net,2004:3337</id>
1156
+ <link href="/blog/2014/10/21/pegurl-js"/>
1157
+ <link rel="replies" href="3337.atom" thr:count="3" thr:updated="2014-11-03T11:38:30-08:00"/>
1158
+ <title>pegurl.js</title>
1159
+ <summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p><a href="http://intertwingly.net/projects/pegurl/">pegurl.js</a> is the result of two days worth of work.  While it is undoubtedly buggy and incomplete, it does pass 255 out of <a href="https://raw.githubusercontent.com/w3c/web-platform-tests/master/url/urltestdata.txt">256 tests</a> and that <a href="http://krijnhoetmer.nl/irc-logs/whatwg/20141021#l-399">last test is wrong</a>.  For comparison: <a href="http://intertwingly.net/stories/2014/10/16/urltest-results/">results from other user agents</a>.</p>
1160
+ <p>Current work products and future work</p></div></summary>
1161
+ <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><svg style="float:right" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
1162
+ <path d="M38,38c0-12,24-15,23-2c0,9-16,13-16,23v7h11v-4c0-9,17-12,17-27c-2-22-45-22-45,3zM45,70h11v11h-11z" fill="#371"></path>
1163
+ <circle cx="50" cy="50" r="45" fill="none" stroke="#371" stroke-width="10"></circle>
1164
+ </svg>
1165
+ <p><a href="http://intertwingly.net/projects/pegurl/">pegurl.js</a> is the result of two days worth of work.  While it is undoubtedly buggy and incomplete, it does pass 255 out of <a href="https://raw.githubusercontent.com/w3c/web-platform-tests/master/url/urltestdata.txt">256 tests</a> and that <a href="http://krijnhoetmer.nl/irc-logs/whatwg/20141021#l-399">last test is wrong</a>.  For comparison: <a href="http://intertwingly.net/stories/2014/10/16/urltest-results/">results from other user agents</a>.</p>
1166
+ <p>Current work products:</p>
1167
+ <ul>
1168
+ <li>Source: <a href="http://intertwingly.net/projects/pegurl/url.js">API</a>, <a href="http://intertwingly.net/projects/pegurl/url.pegjs">grammar</a>; the latter based on <a href="http://pegjs.majda.cz/">PEG.js</a></li>
1169
+ <li><a href="http://intertwingly.net/projects/pegurl/liveview.html">LiveViewer</a>.  Differences mean that either or both of the following are true: (a) pegurl.js doesn’t match the Url Standard or (b) the Url Standard doesn’t match your browser.</li>
1170
+ <li><a href="http://intertwingly.net/stories/2014/10/20/Url.xhtml">Grammar expressed in the form of railroad diagrams</a>.  Produced using <a href="https://twitter.com/peg_js/status/329493915881320448">Gunther Rademacher’s converter</a>.</li>
1171
+ </ul>
1172
+
1173
+ <p>Future work:</p>
1174
+ <ul>
1175
+ <li>The implementation is incomplete, in particular, much of the character encoding logic and IP address parsing is just roughed id at this point.</li>
1176
+ <li>I’d like to propose a number of changes to the test results; mostly to more closely match existing browser behavior, and perhaps where possible to make the implementation logic less convoluted.  Meanwhile, I felt that it was important to have a faithful baseline implemented so that I could experiment with changes and see if there were any unintended consequences to those changes.</li>
1177
+ <li>More tests!  There’s no such thing as too many tests.</li>
1178
+ <li><a href="https://www.w3.org/Bugs/Public/show_bug.cgi?id=25946">Rewrite URL parser</a>.  I suspect that the railroad diagrams (converted to <a href="https://github.com/tabatkins/bikeshed">bikeshed</a>?) plus the parts of the grammar contained in curly braces expressed in prose would be more comprehensible and maintainable than the current state machine approach.</li>
1179
+ </ul></div></content>
1180
+ <updated>2014-10-21T08:17:36-07:00</updated>
1181
+ </entry>
1182
+
1183
+ </feed>
1184
+
1185
+ ---
1186
+
1187
+ feed.format: atom
1188
+ feed.title: Sam Ruby
1189
+ feed.subtitle: It’s just data
1190
+ feed.url: /blog/
1191
+ feed.feed_url: http://intertwingly.net/blog/index.atom
1192
+ feed.updated: >>> DateTime.new( 2017, 5, 26, 3, 36, 44, '-7')
1193
+
1194
+ feed.items[0].title: Badges? We don't need no stinkin' badges!
1195
+ feed.items[0].url: /blog/2017/04/07/Badges-We-dont-need-no-stinkin-badges
1196
+ feed.items[0].guid: tag:intertwingly.net,2004:3356
1197
+ feed.items[0].updated: >>> DateTime.new( 2017, 4, 7, 5, 7, 22, '-7' )