├── .gitignore ├── layouts ├── _spacer.haml ├── _page.haml ├── _article.haml ├── _tweet.haml ├── _about.haml └── default.haml ├── content ├── media │ └── sass │ │ ├── _colors.scss │ │ ├── style.scss │ │ ├── _reset.scss │ │ ├── _border-radius.scss │ │ ├── _pygments.scss │ │ ├── _typography.scss │ │ └── _layout.scss ├── favicon.ico ├── articles │ ├── 2009 │ │ ├── 11 │ │ │ ├── 17 │ │ │ │ └── markdoc.md │ │ │ ├── 02 │ │ │ │ └── bioseq.md │ │ │ └── 06 │ │ │ │ └── capitalism.md │ │ ├── 12 │ │ │ └── 09 │ │ │ │ └── django-boss.md │ │ ├── 07 │ │ │ ├── 20 │ │ │ │ └── http-lock.md │ │ │ ├── 23 │ │ │ │ └── django-orm-trick.md │ │ │ └── 03 │ │ │ │ └── http-post-put-diff.md │ │ ├── 05 │ │ │ ├── 17 │ │ │ │ └── django-staff-databrowse.md │ │ │ └── 18 │ │ │ │ └── json-bioinformatics.md │ │ ├── 06 │ │ │ └── 20 │ │ │ │ └── bureaucratic-breakdown.md │ │ ├── 09 │ │ │ ├── 10 │ │ │ │ └── django-settings-flavours.md │ │ │ ├── 11 │ │ │ │ └── path.md │ │ │ └── 08 │ │ │ │ └── sendfile.md │ │ └── 08 │ │ │ └── 29 │ │ │ └── python-readline-sl.md │ ├── 2010 │ │ ├── 11 │ │ │ └── 11 │ │ │ │ └── sockets-and-nodes-i.md │ │ ├── 05 │ │ │ └── 17 │ │ │ │ └── semblog.md │ │ ├── 01 │ │ │ └── 04 │ │ │ │ └── unlicense.md │ │ └── 07 │ │ │ └── 03 │ │ │ └── nutrition.md │ ├── 2011 │ │ ├── 12 │ │ │ └── 14 │ │ │ │ └── bitcoin.md │ │ ├── 07 │ │ │ └── 20 │ │ │ │ └── joining-dydra.md │ │ └── 01 │ │ │ └── 18 │ │ │ └── up-the-asset.md │ ├── 2012 │ │ ├── 09 │ │ │ └── 07 │ │ │ │ ├── comments │ │ │ │ ├── postit.png │ │ │ │ ├── comments.png │ │ │ │ └── bright_comments.png │ │ │ │ └── comments.md │ │ ├── 08 │ │ │ └── 31 │ │ │ │ ├── m2mbloom │ │ │ │ ├── optimal-k.png │ │ │ │ └── bloom-size-formula.png │ │ │ │ └── slowness.md │ │ ├── 05 │ │ │ └── 21 │ │ │ │ ├── pull-requests │ │ │ │ ├── 1-branch-and-push.png │ │ │ │ ├── 3-add-second-commit.png │ │ │ │ ├── 2-submit-pull-request.png │ │ │ │ └── 4-pull-request-does-not-change.png │ │ │ │ └── pull-requests.md │ │ └── 03 │ │ │ └── 31 │ │ │ └── reification.md │ └── 2013 │ │ ├── 02 │ │ ├── 10 │ │ │ └── smallweb.md │ │ ├── 15 │ │ │ └── activism.md │ │ ├── 21 │ │ │ ├── email │ │ │ │ └── ettr.jpg │ │ │ └── email.md │ │ └── 03 │ │ │ ├── airblade │ │ │ ├── airblade.jpg │ │ │ └── old_hand_dryer.jpg │ │ │ └── airblade.md │ │ ├── 03 │ │ ├── 24 │ │ │ └── propgcc.md │ │ ├── 06 │ │ │ ├── qr-codify │ │ │ │ ├── demo-1.png │ │ │ │ ├── demo-2.png │ │ │ │ └── automator.png │ │ │ └── qr-codify.md │ │ ├── 01 │ │ │ └── big-o.md │ │ ├── 04 │ │ │ └── san-francisco.md │ │ └── 02 │ │ │ └── compilers.md │ │ └── 01 │ │ ├── 21 │ │ ├── paleo.md │ │ └── diet.md │ │ ├── 22 │ │ └── django-objviews.md │ │ └── 24 │ │ └── django-objviews-2.md ├── not-found.haml ├── hiring-me.md ├── index.haml └── rss.rdf.erb ├── lib ├── mtimes.rb ├── xmlns.rb ├── fix_entities.rb └── default.rb ├── Gemfile ├── config.yaml ├── Rakefile ├── Gemfile.lock ├── Rules └── static └── .htaccess /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | output 3 | .bundle 4 | .sass-cache 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /layouts/_spacer.haml: -------------------------------------------------------------------------------- 1 | %div.spacer 2 | %div.spacer-child 3 |   -------------------------------------------------------------------------------- /content/media/sass/_colors.scss: -------------------------------------------------------------------------------- 1 | $teal: #4891b1; 2 | $light-teal: #67a3be; 3 | $grey: #666; -------------------------------------------------------------------------------- /content/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/favicon.ico -------------------------------------------------------------------------------- /content/articles/2013/02/21/email/ettr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2013/02/21/email/ettr.jpg -------------------------------------------------------------------------------- /content/articles/2012/09/07/comments/postit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/09/07/comments/postit.png -------------------------------------------------------------------------------- /content/articles/2013/03/06/qr-codify/demo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2013/03/06/qr-codify/demo-1.png -------------------------------------------------------------------------------- /content/articles/2013/03/06/qr-codify/demo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2013/03/06/qr-codify/demo-2.png -------------------------------------------------------------------------------- /content/articles/2012/08/31/m2mbloom/optimal-k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/08/31/m2mbloom/optimal-k.png -------------------------------------------------------------------------------- /content/articles/2012/09/07/comments/comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/09/07/comments/comments.png -------------------------------------------------------------------------------- /content/articles/2013/02/03/airblade/airblade.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2013/02/03/airblade/airblade.jpg -------------------------------------------------------------------------------- /content/articles/2013/03/06/qr-codify/automator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2013/03/06/qr-codify/automator.png -------------------------------------------------------------------------------- /content/articles/2013/02/03/airblade/old_hand_dryer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2013/02/03/airblade/old_hand_dryer.jpg -------------------------------------------------------------------------------- /content/media/sass/style.scss: -------------------------------------------------------------------------------- 1 | @import "compass/utilities/general/clearfix"; 2 | 3 | @import "_reset"; 4 | @import "_layout"; 5 | @import "_typography"; 6 | @import "_pygments"; 7 | -------------------------------------------------------------------------------- /content/articles/2012/09/07/comments/bright_comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/09/07/comments/bright_comments.png -------------------------------------------------------------------------------- /lib/mtimes.rb: -------------------------------------------------------------------------------- 1 | def mtime(ident_pattern) 2 | regex = Regexp.new(ident_pattern) 3 | items.select { |item| regex === item.identifier }.map { |item| item[:mtime].to_i }.max.to_s 4 | end 5 | -------------------------------------------------------------------------------- /content/articles/2012/08/31/m2mbloom/bloom-size-formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/08/31/m2mbloom/bloom-size-formula.png -------------------------------------------------------------------------------- /content/articles/2012/05/21/pull-requests/1-branch-and-push.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/05/21/pull-requests/1-branch-and-push.png -------------------------------------------------------------------------------- /content/articles/2012/05/21/pull-requests/3-add-second-commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/05/21/pull-requests/3-add-second-commit.png -------------------------------------------------------------------------------- /content/articles/2012/05/21/pull-requests/2-submit-pull-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/05/21/pull-requests/2-submit-pull-request.png -------------------------------------------------------------------------------- /content/articles/2012/05/21/pull-requests/4-pull-request-does-not-change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zacharyvoase/blog.zacharyvoase.com/master/content/articles/2012/05/21/pull-requests/4-pull-request-does-not-change.png -------------------------------------------------------------------------------- /layouts/_page.haml: -------------------------------------------------------------------------------- 1 | %div.content-container 2 | %div.content.page 3 | %p.index-link-container 4 | %a.index-link{:href => "/"} ← All posts 5 | 6 | %h1{:property => "rss:title", :datatype => ""} 7 | = title_of(item) 8 | 9 | %div.contents= find_and_preserve(content) 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | 3 | gem "nanoc" 4 | gem "rake" 5 | gem "json" 6 | gem "haml" 7 | gem "mime-types" 8 | gem "kramdown" 9 | gem "rubypants" 10 | gem "htmlentities" 11 | gem "compass" 12 | gem "rack" 13 | gem "activesupport" 14 | gem "i18n" 15 | gem "adsf" 16 | gem "nokogiri" 17 | gem "systemu" 18 | gem "pygments.rb" 19 | -------------------------------------------------------------------------------- /content/not-found.haml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Not Found 3 | id: not-found 4 | --- 5 | 6 | %div.content-container 7 | %div.content.article 8 | %h1 9 | Whoops. 10 | 11 | %p.date 12 | I tried my best, but couldn’t find that page. 13 | 14 | %p{:style => "text-align: center;"} 15 | You could try heading back to the index. 16 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | text_extensions: [ 'css', 'erb', 'haml', 'htm', 'html', 'js', 'less', 'markdown', 'md', 'php', 'rb', 'sass', 'scss', 'txt' ] 2 | output_dir: output 3 | index_filenames: [ 'index.html' ] 4 | enable_output_diff: false 5 | 6 | base_url: "http://zacharyvoase.com" 7 | 8 | data_sources: 9 | - 10 | type: filesystem_unified 11 | items_root: / 12 | layouts_root: / 13 | -------------------------------------------------------------------------------- /content/hiring-me.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Hiring Me" 3 | kind: page 4 | --- 5 | 6 | If you take a look at the links above, and the articles on this blog, you might 7 | conclude that I'm a smart, passionate developer, who builds and collaborates on 8 | useful software projects, and actively takes part in the OSS community. 9 | 10 | If you hire me (for the short or long term), that creativity and talent will be 11 | directed towards helping your business. 12 | 13 | Get in touch: [z@zacharyvoase.com](mailto:z@zacharyvoase.com) 14 | -------------------------------------------------------------------------------- /layouts/_article.haml: -------------------------------------------------------------------------------- 1 | %div.content-container 2 | %div.content.article 3 | %p.index-link-container 4 | %a.index-link{:href => "/"} ← All posts 5 | 6 | %h1{:property => "rss:title", :datatype => ""} 7 | = title_of(item) 8 | 9 | %p.date{:property => "dc:date", :content => item[:created_at].to_s, :datatype => "xs:date"} 10 | = item[:created_at].strftime("%A ") + item[:created_at].day.to_s 11 | %sup<>= item[:created_at].day.ordinal_suffix 12 | = item[:created_at].strftime(" %B, %Y") 13 | 14 | %div.contents= find_and_preserve(content) 15 | -------------------------------------------------------------------------------- /layouts/_tweet.haml: -------------------------------------------------------------------------------- 1 | %a{:href => "https://twitter.com/share", 2 | :class => "twitter-share-button", 3 | :"data-via" => "zackwurst", 4 | :"data-text" => "“#{item[:title]}”", 5 | :"data-related" => "zackwurst"} Tweet 6 | %a{:href => "https://twitter.com/zackwurst", :class => "twitter-follow-button", 7 | :"data-show-count" => "false"} Words by @zackwurst 8 | %script 9 | !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs"); 10 | -------------------------------------------------------------------------------- /lib/xmlns.rb: -------------------------------------------------------------------------------- 1 | XMLNS = { 2 | "rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 3 | "rdfs" => "http://www.w3.org/2000/01/rdf-schema#", 4 | "content" => "http://purl.org/rss/1.0/modules/content/", 5 | "dc" => "http://purl.org/dc/elements/1.1/", 6 | "foaf" => "http://xmlns.com/foaf/0.1/", 7 | "rss" => "http://purl.org/rss/1.0/", 8 | "sy" => "http://purl.org/rss/1.0/modules/syndication/", 9 | "og" => "http://ogp.me/ns#", 10 | "xsd" => "http://www.w3.org/2001/XMLSchema", 11 | } 12 | 13 | def xmlns 14 | namespaces = {} 15 | XMLNS.each do |key, value| 16 | namespaces["xmlns:#{key}"] = value 17 | end 18 | namespaces 19 | end 20 | -------------------------------------------------------------------------------- /layouts/_about.haml: -------------------------------------------------------------------------------- 1 | %div#meta.content-container{:about => "http://zacharyvoase.com/"} 2 | %p.copy.content 3 | I’m a hacker, and I love to build stuff for the Web. 4 | 5 | %nav.content#links 6 | %ul 7 | %li.first 8 | %a{:rel => "foaf:page", :href => "http://twitter.com/zackwurst"} Twitter 9 | %li 10 | %a{:rel => "foaf:page", :href => "http://lanyrd.com/profile/zacharyvoase/"} Lanyrd 11 | %li 12 | %a{:rel => "foaf:page", :href => "http://github.com/zacharyvoase"} GitHub 13 | %li 14 | %a{:rel => "foaf:page", :href => "https://plus.google.com/114733187952811329250/?rel=author"} G+ 15 | %li 16 | %a{:rel => "foaf:mbox", :href => "mailto:z@zacharyvoase.com"} Email 17 | %li 18 | %a{:rel => "foaf:page", :href => "http://meat.io/"} Meat 19 | -------------------------------------------------------------------------------- /content/index.haml: -------------------------------------------------------------------------------- 1 | --- 2 | title: The Index 3 | id: index 4 | kind: index 5 | --- 6 | 7 | %div.content-container 8 | %div.content 9 | %ul#articles{:typeof => "rdf:Seq", :resource => "http://blog.zacharyvoase.com/#articles"} 10 | - sorted_articles.each do |article| 11 | %li{:rel => "rdf:li", :resource => rel_url_for(article)} 12 | %div{:about => rel_url_for(article), :typeof => "rss:item"} 13 | %span.date{:property => "dc:date", :content => article[:created_at].to_s, :datatype => "xsd:date"}< 14 | %span.date_day= article[:created_at].strftime("%d") 15 | %span.date_month= article[:created_at].strftime("%b") 16 | %span.date_year= article[:created_at].strftime("%Y") 17 | %a.link{:href => rel_url_for(article), :rel => "rss:link"} 18 | %span{:property => "rss:title", :datatype => ""}= title_of(article) 19 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'nanoc3/tasks' 2 | 3 | require 'active_support/inflector' 4 | require 'i18n' 5 | require 'json' 6 | require 'shellwords' 7 | 8 | desc "Create a new article" 9 | task :new_article do |t| 10 | # Get title and slug. 11 | title = (print "Title: "; STDIN.gets).chomp 12 | default_slug = title.parameterize 13 | slug = (print "Slug [#{default_slug}]: "; STDIN.gets).chomp 14 | slug = default_slug if slug.empty? 15 | 16 | # Create the article file. 17 | now = Time.now 18 | datestring = now.strftime("%Y/%m/%d") 19 | filename = "content/articles/#{datestring}/#{slug}.md" 20 | FileUtils.mkdir_p(File.dirname(filename)) 21 | File.open(filename, "w+") do |f| 22 | f.write(< :pre) 12 | return $1 if content =~ /]*>(.*)<\/h1>/i 13 | 14 | return item.identifier.split("/").last 15 | end 16 | 17 | def description_of(item) 18 | content = item.compiled_content(:snapshot => :body) 19 | html = Nokogiri::HTML(content) 20 | unless (summary = html.css('p.summary')).empty? 21 | return summary.text 22 | else 23 | return html.css("p").first.text 24 | end 25 | end 26 | 27 | def rel_url_for(item) 28 | url_for(item).gsub(%r{^#{Regexp.escape(config[:base_url])}}, "") 29 | end 30 | 31 | class Fixnum 32 | def ordinal_suffix 33 | if (11..13).include?(self % 100) 34 | "th" 35 | else 36 | case self.to_i % 10 37 | when 1; "st" 38 | when 2; "nd" 39 | when 3; "rd" 40 | else "th" 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /content/articles/2011/07/20/joining-dydra.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2011-07-20 3 | kind: article 4 | title: "Joining Dydra" 5 | --- 6 | 7 | With jubilance, I'd like to announce that I have officially joined the 8 | [Dydra team][] as a Developer Evangelist. 9 | 10 | [dydra team]: http://dydra.com/about 11 | 12 | Anyone who knows me professionally knows that I love working in open-source, 13 | whether it’s creating, contributing to or even just talking about software. I 14 | enjoy the fraternity, the absence of egos, and the recognition that a line of 15 | code is worth a thousand words. In the coming months I will be working very 16 | closely with the open-source community, aiming both to demonstrate to 17 | developers the power of our platform, and to harvest ideas and feedback for 18 | the product itself. I’ll also be taking care of the client SDKs, initially 19 | focusing on Python and JavaScript (my fortes). 20 | 21 | Aside from being able to work on such a revolutionary product, it’s a joy to be 22 | on a team with some of the most intelligent and talented people I know—and I’m 23 | being sincere. 24 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (3.2.11) 5 | i18n (~> 0.6) 6 | multi_json (~> 1.0) 7 | adsf (1.1.1) 8 | rack (>= 1.0.0) 9 | chunky_png (1.2.7) 10 | colored (1.2) 11 | compass (0.12.2) 12 | chunky_png (~> 1.2) 13 | fssm (>= 0.2.7) 14 | sass (~> 3.1) 15 | cri (2.3.0) 16 | colored (>= 1.2) 17 | fssm (0.2.9) 18 | haml (3.1.7) 19 | htmlentities (4.3.1) 20 | i18n (0.6.1) 21 | json (1.7.6) 22 | kramdown (0.14.2) 23 | mime-types (1.19) 24 | multi_json (1.5.0) 25 | nanoc (3.4.3) 26 | cri (~> 2.2) 27 | nokogiri (1.5.6) 28 | posix-spawn (0.3.6) 29 | pygments.rb (0.3.7) 30 | posix-spawn (~> 0.3.6) 31 | yajl-ruby (~> 1.1.0) 32 | rack (1.5.0) 33 | rake (10.0.3) 34 | rubypants (0.2.0) 35 | sass (3.2.5) 36 | systemu (2.5.2) 37 | yajl-ruby (1.1.0) 38 | 39 | PLATFORMS 40 | ruby 41 | 42 | DEPENDENCIES 43 | activesupport 44 | adsf 45 | compass 46 | haml 47 | htmlentities 48 | i18n 49 | json 50 | kramdown 51 | mime-types 52 | nanoc 53 | nokogiri 54 | pygments.rb 55 | rack 56 | rake 57 | rubypants 58 | systemu 59 | -------------------------------------------------------------------------------- /content/articles/2013/01/21/paleo.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-01-21 3 | kind: article 4 | title: "What Paleo Means (To Me)" 5 | --- 6 | 7 | I want to clarify what I mean when I say ["I eat Paleo"](http://zacharyvoase.com/2013/01/21/diet/#paleo). 8 | 9 | 'Paleo' is a framework for generating *falsifiable* hypotheses about nutrition, 10 | based on the principle that the foods human beings adapted to eat over millions 11 | of years of evolution are definitely safe, and that for new additions to our 12 | diet, there exists a burden of proof to demonstrate safety. 13 | 14 | It is strongly desirable for these hypotheses to be tested through randomized 15 | intervention studies (rather than epidemiological observations); *in vitro* 16 | results should be taken with a pinch of salt but may still provide useful 17 | suggestions for further study. 18 | 19 | It is recognized that safety of a food is not defined solely by short-term 20 | toxicity or lack thereof, but with respect to the entire organism: chronic 21 | effects on the serum and hormonal responses (e.g. glucose and insulin), organs 22 | (e.g. non-alcoholic fatty liver disease), systemic inflammation, the immune 23 | system, gut flora, interactions with other components of the diet, et cetera. 24 | We realize that practical studies can't be perfect, but we can interpret the 25 | evidence we get, and individually decide whether to take the risk. 26 | 27 | 'Paleo' completely defies the typical 'fad diet' label—because it's just 28 | science-based nutrition. 29 | -------------------------------------------------------------------------------- /content/articles/2013/03/06/qr-codify.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-03-06 3 | kind: article 4 | title: "QR Codify: The Most Useful Snippet I've Ever Written" 5 | --- 6 | 7 | **tl;dr**: I wrote a Mac OS X Service which allows you to display the 8 | currently-selected text as an on-screen QR code. Buy it 9 | [here](http://pul.ly/b/59512) for only £1.07. 10 | {: .summary} 11 | 12 | It really annoys me when there's some text on my computer that I need to be 13 | available immediately on my Android phone. Especially when that text is a phone 14 | number or a URL. Fortunately, OS X now has 15 | [Services](https://en.wikipedia.org/wiki/Services_menu#Mac_OS_X), which are 16 | scriptable actions that can be performed on GUI elements by right-clicking. 17 | 18 | I opened up Automator and created the following: 19 | 20 | ![The QR Codify service in Automator](automator.png) 21 | 22 | 23 | Here's a [Gist](https://gist.github.com/zacharyvoase/5102470) for those who 24 | want the raw source code. 25 | 26 | 27 | The service simply reads the selected text from stdin, generates a [Google 28 | Image Chart][] URL for a QR code with that text, downloads it to a temporary 29 | local file and displays it via QuickLook (using the `qlmanage` CLI). It looks 30 | something like this: 31 | 32 | [google image chart]: https://developers.google.com/chart/image/docs/making_charts?hl=en 33 | 34 | ![Demo 1](demo-1.png) 35 | 36 | ![Demo 2](demo-2.png) 37 | 38 | When I'm done with it, I hit the space bar and it goes away. 39 | 40 | If this is something that'd be useful to you, you can grab it 41 | [here](http://pul.ly/b/59512). 42 | -------------------------------------------------------------------------------- /content/media/sass/_reset.scss: -------------------------------------------------------------------------------- 1 | /* (c) 2009, Yahoo! Inc. All rights reserved. 2 | Code licensed under the BSD License: 3 | http://developer.yahoo.net/yui/license.txt 4 | version: 3.0.0 5 | build: 1549*/ 6 | html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;}em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{border:1px solid #000;padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;} -------------------------------------------------------------------------------- /content/articles/2009/07/03/http-post-put-diff.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-07-03 4 | title: "The Difference Between POST and PUT—Get it Right!" 5 | --- 6 | 7 | I’ve been getting pretty annoyed lately by a popular misconception by web 8 | developers that a POST is used to create a resource, and a PUT is used to 9 | update/change one. 10 | 11 | If you take a look at page 55 of [RFC 2616](http://www.ietf.org/rfc/rfc2616.txt) 12 | (“Hypertext Transfer Protocol -- HTTP/1.1”), Section 9.6 (“PUT”), you’ll see 13 | what PUT is actually for: 14 | 15 | > The PUT method requests that the enclosed entity be stored under the 16 | > supplied Request-URI. 17 | 18 | There’s also a handy paragraph to explain the difference between POST and PUT: 19 | 20 | > The fundamental difference between the POST and PUT requests is reflected 21 | > in the different meaning of the Request-URI. The URI in a POST request 22 | > identifies the resource that will handle the enclosed entity. That 23 | > resource might be a data-accepting process, a gateway to some other 24 | > protocol, or a separate entity that accepts annotations. In contrast, the 25 | > URI in a PUT request identifies the entity enclosed with the request -- 26 | > the user agent knows what URI is intended and the server MUST NOT attempt 27 | > to apply the request to some other resource. 28 | 29 | It doesn’t mention anything about the difference between updating/creating, 30 | because that’s not what it’s about. It’s about the difference between this: 31 | 32 | #!python 33 | obj.set_attribute(value) # A POST request. 34 | 35 | And this: 36 | 37 | #!python 38 | obj.attribute = value # A PUT request. 39 | 40 | So please, stop the spread of this popular misconception. Read your RFCs. 41 | -------------------------------------------------------------------------------- /content/articles/2012/09/07/comments.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2012-09-07 3 | kind: article 4 | title: "The Problem With Comments" 5 | --- 6 | 7 | As much as we appreciate well-written comments, most of us would admit to 8 | finding it difficult to keep comments up-to-date. Why is this? Surely, when 9 | you're editing code, the comments are *right there* and easy to update along 10 | with the code? Yet still we forget, overlooking a comment when changing the 11 | fundamental behavior of semantics of the code to which it relates. 12 | 13 | My argument is that this is actually a UX failure on the part of our text 14 | editors; to see why, here's a real-world example of a contextual message which 15 | doesn't change the substance of the object to which it relates: 16 | 17 | ![Post-it notes sit brightly on a dull background.](postit.png) 18 | 19 | The Post-it note is more than just an optional message—it's an admonition, a 20 | sign to whoever is using the object that there is some critical piece of 21 | information that should be considered before proceeding. In fact, it's 22 | deliberately colored in such a way as to *clash* with almost any background 23 | you'd put it against. 24 | 25 | Compare this with how comments are typically displayed: 26 | 27 | ![Comments in code are usually displayed in a grey typeface.](comments.png) 28 | 29 | The comment, as a syntactic structure, is supposed to be a piece of 30 | human-readable information which is ignored by the compiler. But why, then, do 31 | our editors display comments in such a way as to be *ignored by the 32 | programmer?* Imagine if comments were displayed like 33 | this instead: 34 | 35 | ![Brighter comments](bright_comments.png) 36 | 37 | I think there would be a lot fewer meaningless, out-of-date and unhelpful 38 | comments. 39 | -------------------------------------------------------------------------------- /content/rss.rdf.erb: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | <%= sorted_articles.last[:created_at].to_s %> 11 | Zachary Voase 12 | Public Domain 13 | The Blog of Zachary Voase, brought to you in glorious HyperText. 14 | 15 | 16 | <% sorted_articles.each do |article| %> 17 | 18 | <% end %> 19 | 20 | 21 | http://blog.zacharyvoase.com/ 22 | <%= sorted_articles.last[:created_at].to_s %> 23 | 2 24 | daily 25 | Zack’s Blog 26 | 27 | <% sorted_articles.each do |article| %> 28 | 29 | <%= article[:created_at].to_s %> 30 | <%=h url_for(article) %> 31 | <%=h title_of(article) %> 32 | <%=h article.compiled_content(:snapshot => :pre) %> 33 | 34 | <% end %> 35 | 36 | -------------------------------------------------------------------------------- /content/articles/2009/05/17/django-staff-databrowse.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-05-17 4 | title: "Django Tip: Staff-only Access to Databrowse" 5 | --- 6 | 7 | Databrowse has to be one of the most underappreciated Django apps. It’s been 8 | included with Django since 1.0, and it’s really simple to use; just register 9 | some models to a site, point to that site from your URLconf and you get a 10 | fully-featured data browser for free. You can read the databrowse docs 11 | [here](http://docs.djangoproject.com/en/dev/ref/contrib/databrowse/), but 12 | there’s something they don’t mention which I think is really nifty. 13 | 14 | Down at the bottom of that page, it recommends using the `login_required()` 15 | decorator to restrict access to registered users, like so: 16 | 17 | #!python 18 | from django.conf.urls.defaults import * 19 | from django.contrib import databrowse 20 | from django.contrib.auth.decorators import login_required 21 | 22 | urlpatterns = patterns('', 23 | (r'^databrowse/(.*)$', login_required(databrowse.site.root)), 24 | (r'^login/$', 'django.contrib.auth.views.login'), 25 | ) 26 | 27 | But if you want to restrict access to staff (i.e. users who can access the 28 | admin), you’ll have to use another (undocumented) decorator instead. 29 | 30 | #!python 31 | from django.conf.urls.defaults import * 32 | from django.contrib import databrowse 33 | from django.contrib.admin.views.decorators import staff_member_required 34 | 35 | urlpatterns = patterns('', 36 | (r'^databrowse/(.*)$', staff_member_required(databrowse.site.root)), 37 | ) 38 | 39 | Only users with the `is_staff` flag will be able to access databrowse now, and the login form presented is essentially that of the Django admin. Note that `django.contrib.admin` must be in your `INSTALLED_APPS` for this to work. 40 | -------------------------------------------------------------------------------- /content/media/sass/_border-radius.scss: -------------------------------------------------------------------------------- 1 | // Border-radius helps to make it easier to round the corners of your HTML elements 2 | // Sample Usage: 3 | // #container 4 | // +border-radius("5px") 5 | 6 | // All corners 7 | @mixin border-radius($radius) { 8 | border-radius: $radius; 9 | -moz-border-radius: $radius; 10 | -webkit-border-radius: $radius; } 11 | 12 | // Top 13 | @mixin border-radius-top($radius) { 14 | @include border-radius-top-left($radius); 15 | @include border-radius-top-right($radius); } 16 | 17 | // Right 18 | @mixin border-radius-right($radius) { 19 | @include border-radius-top-right($radius); 20 | @include border-radius-bottom-right($radius); } 21 | 22 | // Bottom 23 | @mixin border-radius-bottom($radius) { 24 | @include border-radius-bottom-right($radius); 25 | @include border-radius-bottom-left($radius); } 26 | 27 | // Left 28 | @mixin border-radius-left($radius) { 29 | @include border-radius-top-left($radius); 30 | @include border-radius-bottom-left($radius); } 31 | 32 | // Let's setup the rules so we don't have to repeat ourselves 33 | // These are mixins for this mixin and are re-used above 34 | @mixin border-radius-top-right($radius) { 35 | border-top-right-radius: $radius; 36 | -moz-border-radius-topright: $radius; 37 | -webkit-border-top-right-radius: $radius; } 38 | 39 | @mixin border-radius-bottom-right($radius) { 40 | border-bottom-right-radius: $radius; 41 | -moz-border-radius-bottomright: $radius; 42 | -webkit-border-bottom-right-radius: $radius; } 43 | 44 | @mixin border-radius-bottom-left($radius) { 45 | border-bottom-left-radius: $radius; 46 | -moz-border-radius-bottomleft: $radius; 47 | -webkit-border-bottom-left-radius: $radius; } 48 | 49 | @mixin border-radius-top-left($radius) { 50 | border-top-left-radius: $radius; 51 | -moz-border-radius-topleft: $radius; 52 | -webkit-border-top-left-radius: $radius; } 53 | -------------------------------------------------------------------------------- /Rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler" 4 | Bundler.setup 5 | 6 | preprocess do 7 | system('rsync -a static/ output') # Copy static files. 8 | end 9 | 10 | 11 | # ---- Compilation ---- 12 | 13 | # Don't modify Sass partials. 14 | compile '/media/sass/_*/' do 15 | end 16 | 17 | # Render Sass files with Compass. 18 | require 'compass' 19 | compile '/media/sass/*/' do 20 | load_paths = ['content/media/sass', 'output/media/css'] 21 | load_paths += Compass.sass_engine_options[:load_paths] 22 | filter :sass, :syntax => :scss, :load_paths => load_paths, :style => :compressed 23 | end 24 | 25 | # Don't modify any other files in content/media/. 26 | compile '/media/*/' do 27 | end 28 | 29 | compile '/rss/' do 30 | filter :erb 31 | end 32 | 33 | compile '*' do 34 | case item[:extension] 35 | when 'md' 36 | filter :erb 37 | filter :kramdown 38 | filter :rubypants 39 | filter :fix_entities 40 | filter :colorize_syntax, :default_colorizer => :pygmentsrb, :syntax => :xhtml 41 | snapshot :body 42 | layout '/default/' 43 | when 'erb' 44 | filter :erb 45 | layout '/default/' 46 | when 'haml' 47 | filter :haml, :attr_wrapper => '"' 48 | layout '/default/' 49 | end 50 | end 51 | 52 | 53 | # ---- Routing ---- 54 | 55 | # Don't route Sass partials. 56 | route '/media/sass/_*/' do 57 | end 58 | 59 | # content/media/sass/file.sass => /media/css/file.css 60 | route '/media/sass/*/' do 61 | item.identifier.gsub(%r{^/media/sass/}, "/media/css/").chop + ".css" 62 | end 63 | 64 | # content/media/file.ext => /file.ext 65 | route '/media/*/' do 66 | item.identifier.chop + '.' + item[:extension] 67 | end 68 | 69 | route '/articles/*/' do 70 | if item.binary? 71 | item.identifier.gsub(%r{^/articles/}, "/").chop + "." + item[:extension] 72 | else 73 | item.identifier.gsub(%r{^/articles/}, "/") + "index.html" 74 | end 75 | end 76 | 77 | route '/favicon/' do 78 | '/favicon.ico' 79 | end 80 | 81 | route '/not-found/' do 82 | "/not-found.html" 83 | end 84 | 85 | route '/rss/' do 86 | "/rss.rdf" 87 | end 88 | 89 | # content/file.md => /file/index.html 90 | route '*' do 91 | item.identifier + "index.html" 92 | end 93 | 94 | # ---- Layout ---- 95 | 96 | layout '*', :haml, :attr_wrapper => '"' 97 | -------------------------------------------------------------------------------- /content/articles/2009/07/23/django-orm-trick.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-07-23 4 | title: "Django ORM: Neat (undocumented) trick" 5 | --- 6 | 7 | 8 | UPDATE: 9 | I’m actually working on a 10 | ticket and patch 11 | so that this feature is both documented and regression-tested for future 12 | releases of Django (starting with 1.1). 13 | 14 | 15 | 16 | UPDATE 2: 17 | My patch was accepted and 18 | included as part 19 | of Django 1.1. 20 | 21 | 22 | I just found out something pretty damn cool about Django’s ORM which, as it 23 | happens, is completely undocumented (as far as I can tell). Let’s assume your 24 | model definition is something like: 25 | 26 | #!python 27 | from django.db import models 28 | 29 | class MyModel(models.Model): 30 | 31 | count = models.IntegerField(default=0) 32 | 33 | The following is completely valid, and actually eliminates a lot of the race 34 | conditions that have plagued the Django ORM in the past: 35 | 36 | #!pycon 37 | >>> from django.db.models import F 38 | >>> from myapp.models import MyModel 39 | >>> obj = MyModel(count=4) 40 | >>> obj.save() 41 | >>> obj.count 42 | 4 43 | >>> obj.count = F('count') + 3 44 | >>> obj.save() 45 | >>> obj = MyModel.objects.get(pk=obj.pk) # We need to reload the object. 46 | >>> obj.count 47 | 7 48 | 49 | Typically you’d do something like `obj.count += 3`, but that sets the attribute 50 | to an absolute value, which can be the cause of many a race condition wherein 51 | two threads/processes are editing the same record at a time; the `obj.save()` 52 | would cause one thread to clobber another’s changes. Using `F()`, the SQL 53 | expression instead looks like: 54 | 55 | #!sql 56 | UPDATE "myapp_mymodel" SET "count" = "myapp_mymodel"."count" + 3 WHERE "myapp_mymodel"."id" = 1; 57 | 58 | Here, the ACIDity of the RDBMS ensures that parallel attempts to increment the 59 | count occur without issue. 60 | 61 | This behaviour’s undocumented status means it could break at any minute, and 62 | reloading the object is necessary because otherwise `obj.count` ends up being an 63 | instance of `django.db.models.expressions.ExpressionNode`, even after the object 64 | is saved. 65 | -------------------------------------------------------------------------------- /content/articles/2012/05/21/pull-requests.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2012-05-21 3 | kind: article 4 | title: "Pinned Pull Requests" 5 | --- 6 | 7 | I was taking a look at the [Crossroads I/O](http://www.crossroads.io/) project 8 | today, and stumbled across this in the [FAQ](http://www.crossroads.io/faq): 9 | 10 | > **Why do you not accept pull requests?** 11 | > 12 | > Pull requests can change while being reviewed. This makes it impossible to 13 | > ensure that the code being merged is the same code that has been reviewed and 14 | > discussed, which compromises integrity of the codebase. 15 | > 16 | > Pull requests are meant for delegation of work to sub-maintainers and require 17 | > an established web of trust. We may consider moving to this model in future. 18 | > 19 | > The method for contributing that we are currently using is posting patches to 20 | > the mailing list, as explained 21 | > [here](http://www.crossroads.io/dev:start#toc3). 22 | 23 | What this refers to is the fact that GitHub Pull Requests are dynamic; if you 24 | nominate a branch for a pull request, and then push to that branch, the pull 25 | request is updated with the new commits. I’m no gitmeister, but I’m aware that 26 | git doesn’t merge histories, it merges trees (albeit with an awareness of 27 | history). A branch, in git, is simply a pointer to a tree which is updated as 28 | you make subsequent commits. Therefore, I figured, it should be possible to 29 | create a pull request which references a *specific* commit and doesn’t change 30 | with the branch. 31 | 32 | And it is. 33 | 34 | I create a branch with some commits and push it to GitHub: 35 | 36 | ![](1-branch-and-push.png) 37 | 38 | I submit my pull request using the commit identifier instead of the branch 39 | name. Helpfully, if you just enter the branch name into the input box, GitHub 40 | will display the most recent commit identifier, which you can just 41 | copy-and-paste into the box: 42 | 43 | ![](2-submit-pull-request.png) 44 | 45 | I go back to my terminal and add some more commits to the branch: 46 | 47 | ![](3-add-second-commit.png) 48 | 49 | Note that the pull request hasn’t changed. It was pinned to that commit, rather 50 | than following the branch: 51 | 52 | ![](4-pull-request-does-not-change.png) 53 | 54 | The only issue from GitHub’s end is that it still displays the message about 55 | being able to add commits to the pull request (which I’m pretty sure I can’t). 56 | 57 | I wonder if this will change the attitudes of the Crossroads I/O maintainers to 58 | pull requests. 59 | -------------------------------------------------------------------------------- /content/articles/2013/03/01/big-o.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-03-01 3 | kind: article 4 | title: "Big-O" 5 | --- 6 | 7 | As a self-taught programmer, I've happened upon less formal education in the 8 | art of data structures and algorithms than those who attended university. Most 9 | of what I've learned came through long nights spent on Wikipedia, all of it 10 | directed towards solving problems I was experiencing in a company or project I 11 | was working on. But sometimes this just-in-time learning approach doesn't work 12 | so well. Outside of reading a large textbook, or taking a course on 13 | [Coursera][], it can be hard to discover new knowledge if you aren't in an 14 | academic environment. So that's why I started the [London Big-O 15 | Meetup](http://www.meetup.com/big-o-london/). 16 | 17 | [coursera]: http://www.coursera.org/ 18 | 19 | The aim is simple: to provide a forum where working and studying programmers in 20 | the city can share their favorite data structures and algorithms in a 21 | language-agnostic setting, and thus raise the knowledge level of the entire 22 | group in a way that wouldn't be possible otherwise. We want to hear real 23 | stories of how people apply 'academic' DS&A theory in their daily professional 24 | lives, or cutting-edge research that could provide faster or more accurate ways 25 | of doing difficult things with computers. Anything that can be run on a 26 | computer is up for discussion. The only requirement is that it be accessible to 27 | an audience of people who code on a daily basis. 28 | 29 | The first meetup ran in December 2012, and went pretty well. I gave an intro to 30 | the goals of the group, and a run-through of [Diffie-Hellman Key Exchange][]. 31 | [Tiberiu](http://underrated.org/) spoke about discrete event simulation for 32 | deterministic hardware verification (including how to simulate parallelism), 33 | and we also had a spur-of-the-moment speaker jump in to add some thoughts on 34 | the [frame problem][]. 35 | 36 | [diffie-hellman key exchange]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange 37 | [frame problem]: https://en.wikipedia.org/wiki/Frame_problem 38 | 39 | I'm organizing the second meetup for Thursday the 7th of March, so we can get 40 | it done before I fly off to [PyCon](https://us.pycon.org/2013/) on the 11th. 41 | I've already reached out to speakers and an event space, and I'm waiting on 42 | confirmation, but if you're in London and want to broaden the horizons of your 43 | mind, you should [come along](http://www.meetup.com/big-o-london/events/106036312/). 44 | -------------------------------------------------------------------------------- /content/articles/2009/06/20/bureaucratic-breakdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-06-20 4 | title: "Bureaucratic Breakdown" 5 | --- 6 | 7 | My math teacher, who I admire greatly, showed me a small document he’d written a 8 | while ago detailing the first signs of what he called ‘executive complexity’. 9 | It’s a list of 8 recognizable symptoms which show that your organization (be it 10 | a business, a school or a project) could be dealing with what he referred to as 11 | a ‘Bureaucratic Breakdown’: 12 | 13 | The Invisible Decision 14 | : No-one knows how or where decisions are made (there is no transparency). 15 | 16 | Unfinished Business 17 | : Too many tasks are started but very few carried through to the end. 18 | 19 | Co-ordination Paralysis 20 | : Nothing can be done without checking with a host of interconnected units. 21 | 22 | Nothing New 23 | : There are no radical ideas, inventions or lateral thinking—a general lack of 24 | initiative. 25 | 26 | Pseudo-problems 27 | : Minor issues become magnified out of all proportion. 28 | 29 | Embattled Centre 30 | : The centre battles for consistency and control against local/regional units. 31 | 32 | Negative deadlines 33 | : The deadlines for work become more important than the quality of the work 34 | being done. 35 | 36 | In-tray Domination 37 | : Individuals react to inputs—i.e. whatever gets put in their in-tray—as 38 | opposed to using their own initiative (the difference between being reactive 39 | and proactive). 40 | 41 | Overall, the culture becomes less responsive to the environment and less capable 42 | of changing or adapting. It appears impressive but is out of touch, and often 43 | out of control. These symptoms can accompany a general movement towards the 44 | expansion, consistency, conformity, comparability, control and centralization of 45 | the organization. 46 | 47 | I found it very interesting to read this (I’m reproducing it here almost 48 | word-for-word). I’m lucky enough to be working with a 49 | [fantastic group of people](http://fantastichq.com), and we’re very open and 50 | honest with each other. For me, reading my teacher’s thoughts on this sort of 51 | ‘corporate culture’ gave me something of an insight into the issues which 52 | individuals in large organizations have to deal with on a daily basis. The thing 53 | that struck me most was that all of these phenomena are emergent—they seem to 54 | arise out of a collective will to obey (or lack of will to disobey) the system 55 | rather than the behavior and decisions of any single individual. 56 | -------------------------------------------------------------------------------- /content/articles/2012/08/31/slowness.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2012-08-31 3 | kind: article 4 | title: "Slowness is a Side Effect" 5 | --- 6 | 7 | Functional programmers use the term ‘side effect’ to refer to the impact a 8 | function has on the world around it. Side effects are implicit—they are not 9 | represented in the input arguments or output types of the function. It’s 10 | considered good functional style to write programs without side effects, or at 11 | least with a minimal and predictable number. In imperative programming, we 12 | still try to keep side effects restricted to recognizable places: Ruby uses a 13 | `!` suffix on ‘dangerous’ methods, Python’s core string and number types are 14 | immutable, and the popularity of jQuery even demonstrates the appreciation for 15 | fluent and functional interfaces in the JavaScript community. 16 | 17 | However, it’s easy to fall into the trap of believing that the footprint of a 18 | function begins and ends with its prototype. The question of whether a function 19 | has side effects a simple one: **is the world you are in when the function 20 | terminates equal to the one you were in when it started?** At the level of 21 | fast, atomic operations, like `1 + 1`, this is obviously true. But as a 22 | function gets slower, no matter whether it acts on its environment or not, 23 | **the world you are in when it terminates will have changed significantly since 24 | it started.** 25 | 26 | What does this mean to those of us who don’t care so much about functional 27 | purity? Well, consider the case of defining a capital-I *Interface*, and the 28 | concomitant claim that you can swap out the implementation without changing any 29 | properties of the system. This is often used as an argument for incurring 30 | technical debt, with the promise that the details of an API, database or 31 | service can be ‘abstracted away’. 32 | 33 | The fallacy—or false hope—in this argument is exposed when one considers that 34 | there’s more to an interface than its component function signatures. The 35 | performance and implementation details of various methods have a direct impact 36 | on the experience of their use, and this will, in turn, shape the software 37 | written against them. When you design an interface, it designs back. 38 | 39 | My sole recommendation—and it’s not a panacea—is to take a holistic approach to 40 | implementation and interface. Recognize that performance characteristics *do* 41 | have an effect on how you code. [Write opinionated software][opinionated], 42 | because portable, generic software is usually slow, buggy software. Finally, 43 | take pride in designing robust interfaces and schemas that stand the test of 44 | time. 45 | 46 | [opinionated]: http://gettingreal.37signals.com/ch04_Make_Opinionated_Software.php 47 | -------------------------------------------------------------------------------- /content/articles/2009/09/11/path.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-09-11 4 | title: "Easy Path Manipulation in Python" 5 | --- 6 | 7 | One of the things that really gets on my nerves when working with file paths in 8 | Python is the aesthetically ugly use of `os.path` functions to perform even 9 | simple manipulations of path names. 10 | 11 | Luckily, Jason Orendorff’s `path.py` module provides a very simple wrapper over 12 | these path operations. It contains a single `path` class which you can use like 13 | this: 14 | 15 | #!pycon 16 | >>> from path import path 17 | >>> homedir = path('/Users/zacharyvoase/') 18 | >>> homedir 19 | path('/Users/zacharyvoase/') 20 | 21 | You can manipulate these paths easily, using methods and operator overrides. 22 | `path` also has a lot of the functions from `shutil`, `glob`, `os.path`, et 23 | cetera defined as methods. 24 | 25 | In fact, this module was the inspiration for 26 | [URLObject](http://bitbucket.org/zacharyvoase/urlobject/)’s use of operator 27 | overrides for URL manipulation. 28 | 29 | ### Examples 30 | 31 | #### Path Manipulation 32 | 33 | #!pycon 34 | >>> filename = homedir / 'file.txt' 35 | >>> filename 36 | path('/Users/zacharyvoase/file.txt') 37 | >>> filename.splitext() 38 | (path('/Users/zacharyvoase/file'), '.txt') 39 | 40 | 41 | #### Filesystem Operations 42 | 43 | #!pycon 44 | >>> filename.exists() 45 | False 46 | >>> filename.touch() 47 | >>> filename.exists() 48 | True 49 | 50 | >>> homedir.isdir() 51 | True 52 | >>> homedir.listdir() 53 | [path('/Users/zacharyvoase/.bash_history'), ...] 54 | 55 | 56 | 57 | #### Globs 58 | 59 | #!pycon 60 | >>> homedir.files('*.txt') 61 | [path('/Users/zacharyvoase/file.txt')] 62 | >>> homedir.dirs('Desk*') 63 | [path('/Users/zacharyvoase/Desktop')] 64 | 65 | ### Installing `path` 66 | 67 | There is a slight problem in that Jason’s website, where the original version of 68 | the module was hosted, has disappeared. Luckily, the `path` module lives on in 69 | the [Paver](http://www.blueskyonmars.com/projects/paver/) project, which can be 70 | installed via `easy_install Paver`. You can then use the class with `from 71 | paver.path import path`. 72 | 73 | ### How I Use `path` 74 | 75 | I like to put `path` to use in my Django settings module. Have a look at this 76 | for an example: 77 | 78 | #!python 79 | from paver.path import path 80 | 81 | PROJECT_ROOT = path(__file__).abspath().dirname() 82 | 83 | MEDIA_ROOT = PROJECT_ROOT / 'media' 84 | UPLOAD_ROOT = PROJECT_ROOT / 'uploads' 85 | 86 | TEMPLATE_DIRS = ( 87 | PROJECT_ROOT / 'templates', 88 | ) 89 | 90 | Elsewhere in my Django project, I can then use those paths without having to 91 | import `os.path` and friends. 92 | -------------------------------------------------------------------------------- /content/articles/2009/11/02/bioseq.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-11-02 4 | title: "BioSeq" 5 | --- 6 | 7 | A key problem in the field of Bioinformatics is referring to biological 8 | sequences. A single sequence may be known by many names across many databases on 9 | the web, with distinct ‘IDs’ on different sites and little to tie them together. 10 | 11 | This is where BioSeq comes in. BioSeq is a technology I’m currently working on 12 | to leverage Content-Addressable Storage (CAS), powered by 13 | [Bitcache](http://bitcache.org/), to store and refer to biological sequence 14 | data. Sequences are identified with a URI like the following: 15 | 16 | bioseq:a21268b77c91c67973efa8289cc42a62772d8c33 17 | 18 | The scheme-specific part of a `bioseq:` URI is that sequence’s unique 19 | identifier, generated by applying a cryptographic hash algorithm (currently 20 | SHA1) to the sequence. The sequence itself can then be retrieved by querying a 21 | Bitcache server for the hash, usually via a simple HTTP GET to 22 | `http://bitcache.example.com/-identifier-`. 23 | 24 | The use of cryptographic hashes as identifiers causes the URI to have several 25 | useful properties: 26 | 27 | * A cryptographic hash algorithm will always produce a fixed-length output 28 | regardless of the size of the input, so URIs have a fixed-length. 29 | 30 | * The hashes of two identical sequences will always be identical, and the 31 | hashes of two different sequences have such a small probability of collision 32 | that we may safely ignore this risk. This means `bioseq:` URIs are truly 33 | universal, since a given URI will always refer unambiguously to one 34 | biological sequence. 35 | 36 | * This also gives the property that `bioseq:` URIs are independent of the 37 | server on which the sequences themselves are stored; it is up to the client 38 | which wants to fetch the sequence to resolve the URI into a URL. This can 39 | actually be done with URI prefixes: setting `bioseq:` as a prefix pointing 40 | to a Bitcache server (in an RDF document, for example) will make URI-to-URL 41 | resolution occur automatically. 42 | 43 | * Since two identical sequences will have the same address, redundancy (on the 44 | sequence level) is eradicated entirely. 45 | 46 | * Because changing a sequence will cause its hash to change, the 47 | fetching/updating of sequences will also verify data integrity. 48 | 49 | * CAS makes incremental server-to-server replication very easy, since the 50 | dataset is append-only. See Chris Anderson’s 51 | [blog post](http://jchrisa.net/drl/_design/sofa/_show/post/CouchDB-Implements-a-Fundamental-Algorithm) 52 | on CouchDB replication to see how replication could be implemented on top of 53 | Bitcache. 54 | 55 | The whole concept is still in the planning stages, but the technology to realize 56 | it is all present. In a following blog post, I’ll describe how BioSeq will 57 | integrate with other semantic web technologies, allowing for the creation of a 58 | scalable, distributed infrastructure for storing and querying biological 59 | sequence data. 60 | -------------------------------------------------------------------------------- /content/articles/2013/02/21/email.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-02-21 3 | kind: article 4 | title: "Improving Email, Realistically" 5 | --- 6 | 7 | Every day it seems there's more hype about a new startup that's setting out to 8 | destrominate email as we know it. The thing is, email has been around longer 9 | than even the Web, and I reckon that it'll still be around in its current form 10 | for a long time to come. Rather than trying to revolutionize the way we 11 | communicate, what are some small, incremental, backwards-compatible 12 | improvements we can make to the protocol and the UX of email clients? 13 | 14 | A real problem for me is dealing with a backlog of personal emails over brief 15 | periods of time when I'm busy, traveling, in a different timezone or just plain 16 | tired. So what I really need is not a new interface to my email, but rather 17 | a way of *managing expectations* for people who send me emails. This 18 | expectation management could have the effect of preventing someone from sending 19 | the email in the first place, or perhaps reaching out to me through a different 20 | medium (like telephone, Skype or IM). 21 | 22 | One way of solving this problem would be to build a client which works in 23 | exactly the same way as modern ones, but recognizes patterns in my behavior and 24 | publishes a metric based on info like: 25 | 26 | * my calendar; 27 | * my current timezone; 28 | * the average time I take to respond to other emails; 29 | * the length of the email I need to respond to; etc. 30 | 31 | When someone is drafting an email to me, they should be able to see an 32 | *estimated time to response* live-updating as they write, based on information 33 | published by my email client: 34 | 35 | ![](ettr.jpg) 36 | 37 | The 'information' published would effectively have to be some kind of function 38 | over the length of the e-mail, its sender, my calendar, plus some indication of 39 | my present 'business' (including whether or not I'm likely to be asleep). So 40 | the obvious strategy is to build an API that works at Layer 7 (either DNS 41 | TXT/SRV records, or just HTTP [à la Gravatar][gravatar]) where you can post a 42 | bunch of information in a JSON object and receive a best-guess estimate of the 43 | time I would take to respond. 44 | 45 | [gravatar]: http://en.gravatar.com/site/implement/ 46 | 47 | Such a system has several benefits: 48 | 49 | * It's 100% non-intrusive. You don't need to change your current email client, 50 | or join a waiting list behind 56,000 other people to use it. 51 | * It's decentralized: you can point to your 'response expectation management 52 | provider' from your DNS records, enabling a competitive market of such 53 | providers; some might be free and advert/VC-supported, others would be 54 | 'profitable and proud', others still would be open-source. 55 | * It can be private, if you want it to be: just run your own server. Obviously 56 | there'd be a benefit to using Google's provider—they're really good at 57 | predicting this sort of stuff, and it'd integrate seamlessly with Google 58 | Apps. 59 | 60 | I just had this idea while drinking my espresso this morning, but you can 61 | discuss it further on [Hacker News](https://news.ycombinator.com/item?id=5259032). 62 | -------------------------------------------------------------------------------- /content/articles/2012/03/31/reification.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2012-03-31 3 | kind: article 4 | title: "The Dangers of Reification" 5 | --- 6 | 7 | In a 1987 interview for Woman’s Own magazine, Margaret Thatcher famously 8 | quipped that “there is no such thing as society”. This quote caused a lot of 9 | controversy, such that Thatcher addressed it in her 1993 autobiography, *The 10 | Downing Street Years*: 11 | 12 | > They never quoted the rest. I went on to say: There are individual men and 13 | > women, and there are families. And no government can do anything except 14 | > through people, and people must look to themselves first. It’s our duty to 15 | > look after ourselves and then to look after our neighbour. My meaning, clear 16 | > at the time but subsequently distorted beyond recognition, was that society 17 | > was not an abstraction, separate from the men and women who composed it, but 18 | > a living structure of individuals, families, neighbours and voluntary 19 | > associations. 20 | 21 | It’s true that *society* is an abstraction—an oft useful one, but an 22 | abstraction nonetheless. And therefore when we say ‘it is the responsibility of 23 | *society* to do XYZ,’ we must bear in mind that we are talking about the 24 | components which make up that abstraction: the individuals. When we do err in 25 | our use of it, we are committing the [Fallacy of 26 | Reification](http://en.wikipedia.org/wiki/Reification_(fallacy)). I find it a 27 | lot easier to use standard terms of art from the field of logic rather than 28 | explain the point every time. 29 | 30 | In any dialogue, we hope to improve our own argument by having a skilled 31 | opponent who can point out these mistakes. But we also have to be our own 32 | critics. As libertarians, as much as we like to point out the fallacy of 33 | reifying *society* when committed by left-leaning thinkers, we also have to 34 | avoid the trap of referring to *the market* as a reified entity. 35 | 36 | There is no such thing as the market. There are individual actors, and there 37 | are businesses. The business is reified through the concept of the legal 38 | corporation, which is a little more concrete, but the corporate veil remains a 39 | thin one. When we say “the market will fix this problem”, what we mean to say 40 | is that businesses and individuals will rise to a challenge because it is in 41 | their economic interest to solve it. But this is never guaranteed, and assuming 42 | that a ‘market’ even exists for a given solution is dangerous. 43 | 44 | I’m not arguing in favour of the government running everything; I remain a 45 | lover of economic freedom and I believe that a small government is better than 46 | a large one. But there have been many tragic situations where a government has 47 | cut-and-run from a project they couldn’t manage effectively, expecting ‘the 48 | market’ to pick it up and instantly solve all the issues. The simple 49 | libertarian recommendation of “let the market fix it” needs to be rethought in 50 | terms of establishing robust ‘starting variables’ for common pool resource 51 | management systems that allow a market to grow—a phrase which here means 52 | ‘encourage motivated economic agents to sustainably compete over resources’. 53 | 54 | As participants of a debate, we must be aware of the language we use to think 55 | and talk about these matters, so that we can improve the quality of our 56 | discourse and bring better arguments to the table, even if that means admitting 57 | to a few past mistakes. 58 | -------------------------------------------------------------------------------- /content/media/sass/_pygments.scss: -------------------------------------------------------------------------------- 1 | pre { background: #002B36; color: #93A1A1 } 2 | pre > code { 3 | color: #93A1A1; 4 | .c { color: #586E75 } /* Comment */ 5 | .err { color: #93A1A1 } /* Error */ 6 | .g { color: #93A1A1 } /* Generic */ 7 | .k { color: #859900 } /* Keyword */ 8 | .l { color: #93A1A1 } /* Literal */ 9 | .n { color: #93A1A1 } /* Name */ 10 | .o { color: #859900 } /* Operator */ 11 | .x { color: #CB4B16 } /* Other */ 12 | .p { color: #93A1A1 } /* Punctuation */ 13 | .cm { color: #586E75 } /* Comment.Multiline */ 14 | .cp { color: #859900 } /* Comment.Preproc */ 15 | .c1 { color: #586E75 } /* Comment.Single */ 16 | .cs { color: #859900 } /* Comment.Special */ 17 | .gd { color: #2AA198 } /* Generic.Deleted */ 18 | .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */ 19 | .gr { color: #DC322F } /* Generic.Error */ 20 | .gh { color: #CB4B16 } /* Generic.Heading */ 21 | .gi { color: #859900 } /* Generic.Inserted */ 22 | .go { color: #93A1A1 } /* Generic.Output */ 23 | .gp { color: #93A1A1 } /* Generic.Prompt */ 24 | .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */ 25 | .gu { color: #CB4B16 } /* Generic.Subheading */ 26 | .gt { color: #93A1A1 } /* Generic.Traceback */ 27 | .kc { color: #CB4B16 } /* Keyword.Constant */ 28 | .kd { color: #268BD2 } /* Keyword.Declaration */ 29 | .kn { color: #859900 } /* Keyword.Namespace */ 30 | .kp { color: #859900 } /* Keyword.Pseudo */ 31 | .kr { color: #268BD2 } /* Keyword.Reserved */ 32 | .kt { color: #DC322F } /* Keyword.Type */ 33 | .ld { color: #93A1A1 } /* Literal.Date */ 34 | .m { color: #2AA198 } /* Literal.Number */ 35 | .s { color: #2AA198 } /* Literal.String */ 36 | .na { color: #93A1A1 } /* Name.Attribute */ 37 | .nb { color: #B58900 } /* Name.Builtin */ 38 | .nc { color: #268BD2 } /* Name.Class */ 39 | .no { color: #CB4B16 } /* Name.Constant */ 40 | .nd { color: #268BD2 } /* Name.Decorator */ 41 | .ni { color: #CB4B16 } /* Name.Entity */ 42 | .ne { color: #CB4B16 } /* Name.Exception */ 43 | .nf { color: #268BD2 } /* Name.Function */ 44 | .nl { color: #93A1A1 } /* Name.Label */ 45 | .nn { color: #93A1A1 } /* Name.Namespace */ 46 | .nx { color: #93A1A1 } /* Name.Other */ 47 | .py { color: #93A1A1 } /* Name.Property */ 48 | .nt { color: #268BD2 } /* Name.Tag */ 49 | .nv { color: #268BD2 } /* Name.Variable */ 50 | .ow { color: #859900 } /* Operator.Word */ 51 | .w { color: #93A1A1 } /* Text.Whitespace */ 52 | .mf { color: #2AA198 } /* Literal.Number.Float */ 53 | .mh { color: #2AA198 } /* Literal.Number.Hex */ 54 | .mi { color: #2AA198 } /* Literal.Number.Integer */ 55 | .mo { color: #2AA198 } /* Literal.Number.Oct */ 56 | .sb { color: #586E75 } /* Literal.String.Backtick */ 57 | .sc { color: #2AA198 } /* Literal.String.Char */ 58 | .sd { color: #93A1A1 } /* Literal.String.Doc */ 59 | .s2 { color: #2AA198 } /* Literal.String.Double */ 60 | .se { color: #CB4B16 } /* Literal.String.Escape */ 61 | .sh { color: #93A1A1 } /* Literal.String.Heredoc */ 62 | .si { color: #2AA198 } /* Literal.String.Interpol */ 63 | .sx { color: #2AA198 } /* Literal.String.Other */ 64 | .sr { color: #DC322F } /* Literal.String.Regex */ 65 | .s1 { color: #2AA198 } /* Literal.String.Single */ 66 | .ss { color: #2AA198 } /* Literal.String.Symbol */ 67 | .bp { color: #268BD2 } /* Name.Builtin.Pseudo */ 68 | .vc { color: #268BD2 } /* Name.Variable.Class */ 69 | .vg { color: #268BD2 } /* Name.Variable.Global */ 70 | .vi { color: #268BD2 } /* Name.Variable.Instance */ 71 | .il { color: #2AA198 } /* Literal.Number.Integer.Long */ 72 | } 73 | -------------------------------------------------------------------------------- /content/articles/2013/02/03/airblade.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-02-03 3 | kind: article 4 | title: "Airblade" 5 | --- 6 | 7 | Imagine it's mid-2006, and you manufacture and sell hand dryers. Your product 8 | probably looks something like this: 9 | 10 | Old hand dryer 11 | 12 | Then, one day in October, a company that manufactures vacuum cleaners releases 13 | this machine, and literally turns your industry upside-down: 14 | 15 | Dyson Airblade 16 | 17 | How do you react to something like that? Well, like most people in your 18 | industry at the time, you probably criticize the device for being loud, 19 | weird-looking, and creating a puddle of water on the floor after use. 20 | Unfortunately for you, the die has already been cast. Until now, consumers much 21 | preferred paper towels, and only tolerated your product because premise owners 22 | wanted to save money and waste. Most hand dryers took a minute to dry a pair of 23 | hands, resulting in queues, and customers giving up, just to wipe their hands 24 | on their clothes. But an Airblade can dry a pair of hands completely in 10 25 | seconds. 26 | 27 | A single company, with a little innovation, design and smooth marketing, has 28 | turned a utilitarian product which had to be 'put up with' into a status symbol 29 | for high-end venues and offices. 30 | 31 | * * * * 32 | 33 | Dyson was by no means the first to come up with the general idea, the 34 | Mitsubishi Jet Towel having preceded the Airblade by 13 years. But to ascribe 35 | all of the Airblade's success to pure luck would be a clear mistake—design, 36 | sales and brand matter in the toilet industry as much as any other. 37 | Conceivably, any other company *could* have made a hand dryer with the impact, 38 | impression and technology of the Airblade. The question is: why didn't they? 39 | 40 | I'd guess that garden variety complacency is the answer. Your cash flows are 41 | just fine as they are, and you're not particularly worried about the future. 42 | Premise owners who have already bought your product are now locked into service 43 | contracts, and they've probably amortized the initial cost of the dryers over 44 | several years. High-end establishments would use paper towels anyway, and the 45 | highest-end ones use real cloth towels that get laundered. 46 | 47 | The ease with which the human brain can rationalize away laziness never fails 48 | to surprise. But, as Darwin teaches us, complacency = death. This parable keeps 49 | playing out, in different industries, but always the same way. 50 | 51 | 54 | 55 | As Airblade-like technology becomes cheaper, and second- and third-generation 56 | derivatives thereof (sometimes referred to as 'knock-offs') spring up, how many 57 | mid-market venues do you think will be upgrading? How many greasy spoons and 58 | diners and cafés? This is Gibson's unevenly-distributed future—an economic 59 | tidal wave which sees 'premium' products entering the economy at one end and 60 | dispersing across every stratum within a few years. It's the reason why there 61 | are whole families on welfare carrying iPhones. 62 | 63 | * * * * 64 | 65 | Being a human carries an inevitable amount of economic risk. Being an 66 | innovator, pioneer or entrepreneur carries more still. But it's important to 67 | remember that *non progredi est regredi*—no matter how safe we feel, the Red 68 | Queen effect is always at play, and a Dyson or Apple or Tesla is always working 69 | away in the darkness, ready to eat our lunch. 70 | 71 | Go forth and make Airblades. 72 | -------------------------------------------------------------------------------- /static/.htaccess: -------------------------------------------------------------------------------- 1 | Options +FollowSymlinks +MultiViews 2 | ErrorDocument 404 /not-found.html 3 | 4 | # AddOutputFilterByType DEFLATE text/html application/xhtml+xml text/css text/javascript 5 | FileETag MTime Size 6 | 7 | ExpiresActive On 8 | ExpiresDefault "access plus 1 hour" 9 | ExpiresByType application/xhtml+xml "access plus 5 minutes" 10 | ExpiresByType text/css "access plus 1 year" 11 | ExpiresByType text/javascript "access plus 1 year" 12 | ExpiresByType image/gif "access plus 1 year" 13 | ExpiresByType image/jpeg "access plus 1 year" 14 | ExpiresByType image/png "access plus 1 year" 15 | 16 | RewriteEngine on 17 | RewriteRule ^rss$ rss.rdf 18 | 19 | AddType application/rdf+xml .rdf 20 | AddType application/xhtml+xml .html 21 | AddCharset UTF-8 .html .rdf 22 | 23 | RedirectMatch permanent ^/2010-03-05-deployment-with-uwsgi-and-nginx$ http://blog.zacharyvoase.com/2010/03/05/django-uwsgi-nginx/ 24 | RedirectMatch permanent ^/2010-02-03-django-project-conventions-revisited$ http://blog.zacharyvoase.com/2010/02/03/django-project-conventions/ 25 | RedirectMatch permanent ^/2010-01-31-civil-disobedience$ http://blog.zacharyvoase.com/2010/01/31/civil-disobedience/ 26 | RedirectMatch permanent ^/2010-01-04-why-im-going-public$ http://blog.zacharyvoase.com/2010/01/04/unlicense/ 27 | RedirectMatch permanent ^/2010-01-02-a-nicer-redis-client-for-eventmachine$ http://blog.zacharyvoase.com/2010/01/02/nice-redis-em/ 28 | RedirectMatch permanent ^/2009-12-09-fixing-django-management-commands$ http://blog.zacharyvoase.com/2009/12/09/django-boss/ 29 | RedirectMatch permanent ^/2009-11-17-announcing-markdoc$ http://blog.zacharyvoase.com/2009/11/17/markdoc/ 30 | RedirectMatch permanent ^/2009-11-06-capitalism-or-something-unlike-it$ http://blog.zacharyvoase.com/2009/11/06/capitalism/ 31 | RedirectMatch permanent ^/2009-11-02-bioseq$ http://blog.zacharyvoase.com/2009/11/02/bioseq/ 32 | RedirectMatch permanent ^/2009-11-02-bioinformatics-and-the-semantic-web$ http://blog.zacharyvoase.com/2009/11/02/bioinformatics-semweb/ 33 | RedirectMatch permanent ^/2009-09-11-easy-path-manipulation-in-python$ http://blog.zacharyvoase.com/2009/09/11/path/ 34 | RedirectMatch permanent ^/2009-09-10-django-settings-flavours$ http://blog.zacharyvoase.com/2009/09/10/django-settings-flavours/ 35 | RedirectMatch permanent ^/2009-09-08-serving-authenticated-static-files-with-django$ http://blog.zacharyvoase.com/2009/09/08/sendfile/ 36 | RedirectMatch permanent ^/2009-08-29-getting-a-proper-readline-module-for-python-on-snow-leopard$ http://blog.zacharyvoase.com/2009/08/29/python-readline-sl/ 37 | RedirectMatch permanent ^/2009-08-20-openpgp-for-complete-beginners$ http://blog.zacharyvoase.com/2009/08/20/openpgp/ 38 | RedirectMatch permanent ^/2009-07-23-django-orm-neat-undocumented-trick$ http://blog.zacharyvoase.com/2009/07/23/django-orm-trick/ 39 | RedirectMatch permanent ^/2009-07-20-idea-distributed-http-lock-server$ http://blog.zacharyvoase.com/2009/07/20/http-lock/ 40 | RedirectMatch permanent ^/2009-07-03-the-difference-between-post-and-put-get-it-right$ http://blog.zacharyvoase.com/2009/07/03/http-post-put-diff/ 41 | RedirectMatch permanent ^/2009-06-20-bureaucratic-breakdown$ http://blog.zacharyvoase.com/2009/06/20/bureaucratic-breakdown/ 42 | RedirectMatch permanent ^/2009-05-18-why-json-will-save-bioinformatics$ http://blog.zacharyvoase.com/2009/05/18/json-bioinformatics/ 43 | RedirectMatch permanent ^/2009-05-17-django-tip-staff-only-access-to-databrowse$ http://blog.zacharyvoase.com/2009/05/17/django-staff-databrowse/ 44 | -------------------------------------------------------------------------------- /content/articles/2013/01/22/django-objviews.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-01-22 3 | kind: article 4 | title: "Object-based Views in Django" 5 | --- 6 | 7 | I'm not a fan of [class-based views][cbv], much for the same reason I prefer C 8 | over C++—why use classes when simple functions and data structures will 9 | suffice? I think it stems from misinterpretation of [DRY][]; it's a way of 10 | reducing complexity, but it's often interpreted as a call to effectively gzip 11 | your code, and taken to the extreme you get highly nested structures, amounting 12 | to a [Huffman code][] of your business logic. 13 | 14 | [cbv]: https://docs.djangoproject.com/en/dev/topics/class-based-views/ 15 | [dry]: http://www.artima.com/intv/dry.html 16 | [huffman code]: https://en.wikipedia.org/wiki/Huffman_coding 17 | 18 | Additionally, I'm worried that many Python developers view class syntax as a 19 | way of producing DSLs instead of creating actual type hierarchies. In my 20 | experience, such clever programming leads to a much higher difficulty debugging 21 | errors, with only marginal improvements (or often drops) in code legibility. So 22 | here's a classless (hehe) alternative to solving the code reuse and 23 | authoritative definition problems. 24 | 25 | The Django docs include a section on [generic form handling][cbv-forms], so 26 | let's start with that (because it's a pretty common use case). 27 | 28 | [cbv-forms]: https://docs.djangoproject.com/en/dev/topics/class-based-views/generic-editing/ 29 | 30 | Here's what I want the basic API to look like: 31 | 32 | #!python 33 | # myapp/views.py 34 | from objviews import ModelResource 35 | 36 | from myapp.models import Contact # Assume this exists. 37 | 38 | contact = ModelResource(name='contact', model=Contact) 39 | 40 | From your URLconf: 41 | 42 | #!python 43 | from django.conf.urls import patterns, include 44 | from myapp.views import contact 45 | 46 | urlpatterns = patterns('myapp.views', 47 | (r'^contacts/', include(contact.urls)), 48 | # Effectively produces these rules (URL => name): 49 | # contacts/ => contact_list 50 | # contacts/add/ => contact_add 51 | # contacts/\d+/ => contact_detail 52 | # contacts/\d+/edit/ => contact_edit 53 | # contacts/\d+/delete/ => contact_delete 54 | ) 55 | 56 | Here's a more sophisticated example, featuring a different URL identifier (slug 57 | instead of numeric): 58 | 59 | #!python 60 | from objviews import ids # Effectively an enum of URL-suitable regexes. 61 | 62 | # Here you'd only get the /contacts/, /contacts/add/ and /contacts// 63 | # URL patterns being generated. 64 | contact = ModelResource(name='contact', model=Contact, id=ids.SLUG, 65 | actions=('list', 'add', 'detail')) 66 | 67 | Note that you configure the 'resource' object through keyword parameters, not 68 | subclassing and extending. This limits the possibilities for extension, but 69 | thereby keeps things constrained and more easily debuggerable. You might be 70 | personally super-familiar with Python's multiple inheritance mechanism, but I 71 | bet not every developer on your team is—and it still adds a layer of 72 | complication when stuff breaks (think: mixins). When you do need extension and 73 | polymorphic behavior, object-based views can create sub-objects and delegate to 74 | them—favouring [composition over inheritance][coi]. 75 | 76 | [coi]: https://en.wikipedia.org/wiki/Composition_over_inheritance 77 | 78 | You can also build a number of standard URLs from a single definition by using 79 | ``include()`` and having a ``urls`` property on the object. If you wanted the 80 | same thing for a class-based view, you'd have to implement 'class properties' 81 | (that is, descriptors on the metaclass—it sounds complicated because it is), or 82 | otherwise call a classmethod to generate the ``patterns`` object. And with 83 | objects, because you can directly call them or their methods, you get to skip 84 | all of the ``as_view()`` malarkey, too. 85 | 86 | I'm aiming to provide a working prototype of an object-based view, but for now 87 | I want to just put the idea and potential API out there. 88 | -------------------------------------------------------------------------------- /content/articles/2013/02/15/activism.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-02-15 3 | kind: article 4 | title: "Activism" 5 | --- 6 | 7 | You might come down on either side in the positive discrimination debate, and 8 | you may have your own opinion on the paucity of women in the tech community, 9 | but one of the aspects of our engineering culture that makes me so proud is 10 | that we can (if we want) have rational discussions about the benefits and 11 | drawbacks of various approaches, and things largely remain civil. 12 | 13 | But then once in a while someone comes along and [threatens that peace](http://www.kernelmag.com/yiannopoulos/4115/put-a-sock-in-it-you-dickless-wonders/): 14 | 15 | > Is there anything more utterly fucking boring than well-meaning commentators 16 | > tweeting the obligatory “Why are there no women on stage?” at tech 17 | > conferences? 18 | > … 19 | > No matter the significance, newsworthiness or even comic potential of what’s 20 | > happening on stage, a male tech blogger can always be relied upon to bleat 21 | > out the same, tired old commentary as if he were a bold social reformer, 22 | > rather than the bland, craven hack he so often is. 23 | > … 24 | > […]this is the technology industry: there are more men in it because the male 25 | > mind is, in general, better primed with the sorts of skills the industry 26 | > values; men are simply better suited to most technology jobs. 27 | > 28 | > Women therefore tend to work in roles that require finesse and communicative 29 | > skills, where they pop up in this world at all. 30 | 31 | These are the words of a man called Milo Yiannopoulos. I could rant for hours 32 | about the flaws in his proposition (believe me when I say his rant continues 33 | with accelerating grimness), but I won't. Instead, I want to make a plea to the 34 | companies highlighted in [his 35 | biography](http://www.kernelmag.com/author/yiannopoulos/): 36 | 37 | > Milo specialises in privacy, piracy, technology start-ups, internet culture 38 | > and the media. He was previously Consulting Editor (Technology) for 39 | > **Telegraph.co.uk** and a European contributing editor at **TechCrunch**. 40 | > 41 | > His writing has also appeared in the **Wall Street Journal**, **the Times**, 42 | > **the Observer**, **the Guardian**, **WIRED**, **the Spectator**, **Corriere 43 | > della Sera**, **Directors’ Guild of America Quarterly**, the **500 Startups** 44 | > blog, **Management Today**, **L’Osservatore Romano** and many other 45 | > publications in Europe and America. 46 | > 47 | > Milo is Chief Feature Writer for **The Catholic Herald**. He is an advisor to 48 | > citizen journalism news service **Blottr** and he also serves as mentor on 49 | > the **500 Startups** and **Springboard** accelerator programs, where he 50 | > advises technology start-ups on marketing and public relations. 51 | > 52 | > In 2011 and again in 2012, Milo was named one of the 100 most influential 53 | > people in Britain’s digital economy by **WIRED** magazine. He was inducted 54 | > into **Courvoisier’s** The Future 500 in 2012 and he has been profiled by the 55 | > **Observer**, **Forbes** and others. 56 | 57 | If you own or manage an organization on this list, I'm speaking to you 58 | directly. **After the dust has settled on this one, there will be two 59 | categories of company: those who spoke up and disavowed any connection with Mr. 60 | Yiannopoulos, and those who did not.** I hope for your sake you have the wisdom 61 | to position yourself on the right side of history in this matter. 62 | 63 | Incidentally, I checked the [500 Startups](http://500.co/) mentor list, 64 | and couldn't find a mention of Mr. Yiannopoulos's name anywhere. 65 | 66 | ## Community 67 | 68 | The Internet is the great enabler, and by empowering everyone, it levels the 69 | playing field, such that no single individual is inherently stronger or weaker 70 | than any other. So if we want to confront threats to our peace like this, and 71 | hold them to account, we have to present a united front. 72 | 73 | I want all bigots and trolls and uncivil people out there to heed these words: 74 | we are more numerous than you, and together we have the ability to shut you 75 | out. If you want to work with us or for us *ever again*, if you want to attend 76 | our conferences and talk at our meetups and drink our sponsored beer and write 77 | for our journals, you need to play by the rules of civil discussion. 78 | 79 | Zack out. 80 | -------------------------------------------------------------------------------- /content/media/sass/_typography.scss: -------------------------------------------------------------------------------- 1 | @import "_colors"; 2 | 3 | $serif: ff-meta-serif-web-pro, Georgia, "Liberation Serif", serif; 4 | $sans-serif: futura-pt, Futura, "Gill Sans", "Gill Sans MT", "Liberation Sans", "Trebuchet MS", "DejaVu Sans", "Bitstream Vera Sans", Verdana, sans-serif; 5 | $monospace-inline: "Courier", "Courier 10 Pitch", "Courier New", "monospace"; 6 | $monospace-block: "Menlo", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier", "Courier 10 Pitch", "Courier New", "monospace"; 7 | 8 | @mixin dark-bg { 9 | color: white; 10 | text-decoration: none; 11 | 12 | a, a:visited, a:active { color: $teal * 3; } 13 | } 14 | 15 | .titling { 16 | font-family: $sans-serif; 17 | font-weight: 700; 18 | letter-spacing: 1px; 19 | text-transform: uppercase; 20 | text-align: center; 21 | } 22 | 23 | .summary { 24 | font-style: italic; 25 | } 26 | 27 | #header { 28 | h1 { 29 | color: white; 30 | font-size: 38px; 31 | line-height: 44px; 32 | @extend .titling; 33 | 34 | @include mobile { 35 | font-size: 24px; 36 | line-height: 32px; 37 | } 38 | 39 | a { 40 | color: inherit; 41 | text-decoration: none; 42 | &:hover { text-decoration: none; } 43 | } 44 | 45 | .prefix { 46 | font-size: 16px; 47 | line-height: 20px; 48 | } 49 | } 50 | } 51 | 52 | #meta { 53 | text-align: center; 54 | text-transform: uppercase; 55 | font-size: 14px; 56 | line-height: 20px; 57 | 58 | p.copy { 59 | font-family: $serif; 60 | text-transform: none; 61 | font-size: 18px; 62 | line-height: 24px; 63 | } 64 | } 65 | 66 | #links { 67 | @extend .titling; 68 | color: $grey; 69 | font-size: 14px; 70 | 71 | ul { text-align: center; } 72 | a, a:visited { color: $grey; } 73 | } 74 | 75 | #footer p { 76 | @include dark-bg; 77 | @extend .titling; 78 | font-size: 12px; 79 | } 80 | 81 | #articles { 82 | li { 83 | .date { 84 | @extend .titling; 85 | color: $grey; 86 | font-size: 12px; 87 | text-align: right; 88 | font-weight: normal; 89 | @include mobile { 90 | text-align: left; 91 | } 92 | } 93 | 94 | .date_month { 95 | display: inline-block; 96 | width: 32px; 97 | text-align: center; 98 | } 99 | 100 | .link { 101 | @extend .titling; 102 | color: #000; 103 | text-align: left; 104 | font-size: 13px; 105 | @include mobile { font-size: 18px; } 106 | } 107 | } 108 | } 109 | 110 | div.article, div.page { 111 | text-rendering: optimizeLegibility; 112 | 113 | a.index-link { 114 | font-size: 12px; 115 | text-transform: uppercase; 116 | } 117 | 118 | h1 { 119 | @extend .titling; 120 | font-size: 24px; 121 | line-height: 32px; 122 | } 123 | 124 | p.date { 125 | color: $grey * 0.5; 126 | font-size: 14px; 127 | text-align: center; 128 | } 129 | } 130 | 131 | body { 132 | font-family: $sans-serif; 133 | font-size: 16px; 134 | line-height: 26px; 135 | color: #333; 136 | 137 | .contents { 138 | font-family: $serif; 139 | text-align: justify; 140 | 141 | h1, h2, h3, h4, h5 { 142 | @extend .titling; 143 | text-align: left; 144 | } 145 | h2 { font-size: 20px; } 146 | h3 { font-size: 18px; } 147 | h4 { font-size: 16px; } 148 | h5 { font-size: 15px; } 149 | p { margin-bottom: 26px; } 150 | } 151 | } 152 | 153 | a { 154 | color: #333; 155 | text-decoration: none; 156 | 157 | &:hover { text-decoration: underline; } 158 | &:visited, &:active { color: #333; } 159 | 160 | // Links in the body of an article/page. 161 | .article .contents &, .page .contents & { 162 | text-decoration: underline; 163 | 164 | &:visited { color: $grey; } 165 | } 166 | } 167 | 168 | code { 169 | font-family: $monospace-inline; 170 | font-size: 14px; 171 | line-height: 24px; 172 | } 173 | 174 | pre { 175 | white-space: normal; 176 | & > code { 177 | color: $grey * 0.5; 178 | font-family: $monospace-block; 179 | font-size: 12px; 180 | line-height: 18px; 181 | white-space: pre; 182 | } 183 | } 184 | 185 | blockquote { color: $grey / 2; } 186 | dl dt { font-weight: bold; } 187 | .article .contents { 188 | ins, del { @include dark-bg; } 189 | } 190 | sup { vertical-align: super; } 191 | -------------------------------------------------------------------------------- /content/articles/2010/05/17/semblog.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2010-05-17 3 | kind: article 4 | title: "The Semantic Blog" 5 | --- 6 | 7 | You might see that I’m up and running on a new blog of late; I just wanted to 8 | take some time to go over its architecture and some neat features. 9 | 10 | 11 | ## Cool URIs 12 | 13 | Firstly, I’ve changed the URI structure for my posts. Before I had URIs like: 14 | 15 | /2009-08-20-openpgp-for-complete-beginners 16 | 17 | Now they’re more like: 18 | 19 | /2009/08/20/openpgp/ 20 | 21 | Yes, I know. [Cool URIs don’t change](http://www.w3.org/Provider/Style/URI). But 22 | the URIs I had before weren’t cool. These ones are, and I don’t see any reason 23 | to change them in the future. 24 | 25 | 26 | ## Powered by nanoc3 27 | 28 | My previous blog was powered by my very own [Markdoc](http://markdoc.org/), 29 | which I’m still quite proud of. Unfortunately it doesn’t really accommodate the 30 | blogging model (i.e. chronologically-ordered content) very well. 31 | 32 | This blog is powered by [nanoc3](http://nanoc.stoneship.org/), which is a great 33 | Ruby utility for static publishing. It provides much more control over how the 34 | content is built into a HTML site, and it’s definitely a better fit for 35 | blogging. My templates are written in [Haml](http://haml-lang.com), stylesheets 36 | in [Sass](http://sass-lang.com/), and content in 37 | [Markdown](http://daringfireball.net/projects/markdown/). 38 | 39 | 40 | ## No Comment 41 | 42 | There’s no real reason to host my own peanut gallery. Disqus comments break with 43 | proper XHTML (that’s XHTML served with `Content-Type: application/xhtml+xml`), 44 | and they’re quite ugly. If you really feel the need to get something off your 45 | chest, I’m sure someone on [Hacker News](http://news.ycombinator.com/) cares. If 46 | you have a question, e-mail me, and if I feel it’s important enough, I’ll update 47 | the relevant post. 48 | 49 | 50 | ## Public 51 | 52 | It’s worth pointing out again that the contents of this blog—design, code and 53 | text—have all been released into the Public Domain. You are free to remix, reuse 54 | and redistribute whatever you find here, without any encumbrance or attribution 55 | requirement. 56 | 57 | 58 | ## Semantic 59 | 60 | I saved the best ’til last. Take a look at the source of this page. You’ll 61 | notice a few things that aren’t so common—the doctype: 62 | 63 | #!html 64 | 66 | 67 | The big set of namespace declarations: 68 | 69 | #!html 70 | 80 | 81 | `property` attributes on almost every text-containing element: 82 | 83 | #!html 84 |

85 | The Blog of Zachary Voase, brought to you in glorious HyperText. 86 |

87 | 88 | This is called [RDFa](http://en.wikipedia.org/wiki/RDFa), and it is a big part 89 | of the web’s future. It is a standard for embedding RDF triples in the natural 90 | structure of your HTML documents, such that they provide semantic context 91 | *and* can easily be parsed out into raw triples later. 92 | 93 | 94 | ### Demonstration 95 | 96 | To get a valid [RSS 1.0](http://web.resource.org/rss/1.0/) feed for this site: 97 | 98 | #!bash 99 | rapper -q -i rdfa -o rdfxml-abbrev 'http://blog.zacharyvoase.com/' 100 | 101 | 102 | You’ll need to 103 | install rapper. 104 | 105 | 106 | Since RSS 1.0 is just an RDF ontology, you can embed its semantics into the 107 | fabric of the page itself. `http://blog.zacharyvoase.com/` *is* an RSS 1.0 feed, 108 | although represented in XHTML+RDFa instead of RDF/XML. Ideally I’d like to have 109 | the following retrieve the XML version of the feed: 110 | 111 | #!bash 112 | curl -H 'Accept: application/rss+xml' 'http://blog.zacharyvoase.com/' 113 | 114 | I’ll set this up once I figure out all the Apache options. 115 | -------------------------------------------------------------------------------- /content/articles/2013/03/04/san-francisco.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-03-04 3 | kind: article 4 | title: "San Francisco" 5 | --- 6 | 7 | As I write this, I'm sat on a Boeing 747-400 from SFO to LHR, planning out a 8 | decision tree with a million branches for my immediate and long-term future. 9 | I'm returning home after attending a job interview which didn't pan out in the 10 | end, determined to find a way to get into the San Francisco Bay Area in the 11 | next few months, and stay there. 12 | 13 | * * * * * 14 | 15 | Filmmakers have Hollywood; financiers have New York City; wine makers have 16 | Champagne; hackers have San Francisco. This, of course, is not to say it is 17 | impossible to produce code outside of the Bay Area—some wonderful wines come 18 | from Bordeaux, Germany and Italy; great films are made in Berlin and Canada; 19 | London has a long, strong history of financial innovation. But an *economy of 20 | agglomeration* undeniably exists in San Francisco and its neighbouring towns 21 | (Palo Alto, Mountain View, etc.). Every day, apps and websites and gadgets are 22 | conceived, designed, managed, built, tested and sold on a small peninsula which 23 | can be driven across entirely in little over an hour. 24 | 25 | Certainly, the men and women who do this work demand a pretty penny for it. But 26 | the capital available to pay them is as abundant as you'd expect, because 27 | technological innovation in particular is the art of force multiplication—if 28 | you can build something which saves millions of people several minutes each 29 | day, why *shouldn't* you be rewarded handsomely for it? 30 | 31 | Many of the ideas executed upon are silly. If they weren't, someone else would 32 | have thought of them already. Even more of the executions are imperfect. 33 | Failure is the median. But success is still the mean. 34 | 35 | If you're a good programmer, there's nowhere else on Earth where you can rack 36 | up as much XP as the Valley. You won't be paid this much for what seems to be a 37 | 'simple' job anywhere else, short of working in finance. You certainly can't 38 | rock up anywhere else in the world *without* any letters after your name and 39 | expect a six-figure salary. 40 | 41 | The hardware, software and techniques that people are using in San Francisco 42 | *today* are the those everyone else will be using in six months. If you're 43 | looking to build a product, and it fails with the über-early adopter crowd of 44 | San Francisco, it probably won't work elsewhere. If it works in SF, it doesn't 45 | matter if it succeeds elsewhere, because people here have enough money to make 46 | you rich anyway. 47 | 48 | Even multi-national companies distinguish between their SF workers and foreign 49 | counterparts. If you're working as a Software Engineer at Google in London, you 50 | can probably expect to earn (*gross*) 64% of what your Mountain View-based 51 | colleagues make (£45k ~= $70k vs. $110k+), whilst laying out for a cost of 52 | living that's 16–22% higher than someone in the city of San Francisco, and 53 | probably paying more taxes too. In 'real-world' terms, this means you're going 54 | to be near-unable to save, you'll have a smaller house/apartment, further away 55 | from the centre of town, with fewer labor-saving amenities, and an overall 56 | poorer quality of life (including the shitty weather). For exactly the same 57 | job. 58 | 59 | San Francisco really is the land of opportunity. 60 | 61 | * * * * * 62 | 63 | Those who know me well know I've been strict Paleo for around a year, and 64 | weightlifting for about two (although with varying strictness of routine). 65 | Another big drive for me in SF is the large number of people doing Paleo, 66 | CrossFit, [keto](/2013/01/21/diet/), powerlifting and/or Olympic lifting, and 67 | even just the general awareness of these phenomena amongst those not doing 68 | them. I can get bunless lettuce-wrap burgers without weird looks (or plain 69 | ignorance on the part of serving staff). I can talk to other people about 70 | squats and deadlifts without blank stares. 71 | 72 | * * * * * 73 | 74 | My goal in writing this is to both explain and understand my motivations for 75 | pursuing San Francisco as a location to live and work. I don't expect everyone 76 | to agree with me—but they don't have to. 77 | 78 | I write this as a 20-year old programmer who has so far made a living out of 79 | not listening to 'good' advice. I'm scared about my future: I'm not guaranteed 80 | a job or a work visa at this stage. But if things don't work out in the next 6 81 | months, I'll keep trying, because they might come through in the next 12, or 82 | 18, or 24. I have nothing to lose. 83 | -------------------------------------------------------------------------------- /content/articles/2010/01/04/unlicense.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2010-01-04 4 | title: "Why I’m Going Public" 5 | --- 6 | 7 | I just made the decision to unlicense my Markdown-based wiki management software 8 | [Markdoc][], and re-release it into the public domain. I’m about to start the 9 | lengthy but valuable process of unlicensing [all of my software][], and I felt 10 | it would be a good idea to explain my actions, and perhaps convince others to 11 | follow suit. 12 | 13 | [markdoc]: http://markdoc.org/ 14 | [all of my software]: http://bitbucket.org/zacharyvoase/ 15 | 16 | The trail that led me here began several years ago, when I wrote my first 17 | complete program in Python. I licensed it under the GNU GPL, because as far as I 18 | could tell that was *the* open-source software license (I just wasn’t aware of 19 | any others). I read the full text of the GPL, and it seemed reasonable enough to 20 | me. I did always have a nagging in the back of my head at the time, that the 21 | ‘viral’ nature of the license might be harmful in the long term, but I went with 22 | it anyway. Nothing I had written at that stage was worthy of being redistributed 23 | by anyone else, so I had little to worry about. 24 | 25 | My encounter with Django convinced me to start using MIT/X11 as my license of 26 | choice. It turns out a lot of other people shared my opinion on the GPL, and 27 | that the choice of license really *did* have real-world consequences. Like Mac 28 | OS X coming with a sucky readline library, or stupidly long installations 29 | because a developer couldn’t include a required library with their software for 30 | fear of being sued. As I saw it, GPL was a different kind of free software — it 31 | was freedom **whether you like it or not**. 32 | 33 | By this point I was just a step away from public domain. Not being a lawyer, I 34 | had neither the incentive nor the ability to draft a comprehensive ‘unlicense’ 35 | that would declare my code as public domain. The MIT license was suiting me 36 | fine, and I didn’t expect anyone to even want to redistribute my software. After all, this is the era of the dynamic language, and it’s relatively simple to just declare an external dependency and call it a day. But then came the revelation. 37 | 38 | 39 | ## Software Freedom 40 | 41 | The word ‘free’ is bandied about a lot today. Sometimes it’s written as ‘Free’, 42 | because with capitalization it is no longer a word, but a platonic ideal. The 43 | FSF describes free software as “free as in speech, not as in beer” (although 44 | it’s usually both). I, however, like to consider the *spirit* of freedom. And, 45 | [unlike the FSF][fsd], I’m not going to waste over 12,000 bytes telling you what 46 | it is. 47 | 48 | [fsd]: http://www.gnu.org/philosophy/free-sw.html 49 | 50 | 51 | ### The Spirit of Freedom 52 | 53 | So I’ve written a piece of software. 54 | 55 | * I’m not making any promises about this piece of software. 56 | * You are free to do **whatever the fuck** you want *with* this piece of software. 57 | * You are free to do **whatever the fuck** you want *to* this piece of software. 58 | 59 | And isn’t *that* what being free is all about? Not having to worry that you 60 | might be breaking a rule? Even though I think the MIT license is relatively loose, it still forces requirements on the users: 61 | 62 | Permission is hereby granted ...snip... subject to the following 63 | conditions: 64 | 65 | The above copyright notice and this permission notice shall be 66 | included in all copies or substantial portions of the Software. 67 | 68 | OK, so it’s a small requirement, but even this turns out to be a big issue when 69 | you’re dealing with mixing and redistributing multiple pieces of software with 70 | various licenses. 71 | 72 | The way public domain works is as follows: 73 | 74 | * You see my code. 75 | 76 | * You like my code. 77 | 78 | * You copy-and-paste my code into your application, and if you’re gracious 79 | enough, you send me a tweet or an e-mail to let me know. Perhaps you even 80 | acknowledge my contribution in an AUTHORS file somewhere. 81 | 82 | But you don’t have to. 83 | 84 | Now that’s what I call freedom. 85 | 86 | 87 | ## The Unlicense 88 | 89 | [The Unlicense][] has recently been published, and it’s provided the impetus I needed to start putting my software into the public domain (henceforth known as ‘pubdomming’). I can now `cp`-and-go as I could with the GPL and MIT licenses. The text of the license is short and sweet, and remains perfectly aligned with the spirit of freedom. So why don’t you unlicense *your* code? 90 | 91 | [the unlicense]: http://unlicense.org/ 92 | -------------------------------------------------------------------------------- /content/articles/2013/02/10/smallweb.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-02-10 3 | kind: article 4 | title: "The Web Is Becoming Smalltalk" 5 | --- 6 | 7 | The concept of a 'web page' is quickly becoming meaningless. I believe there's 8 | a new way to look at the Web and the browser, and synthesizing it with old 9 | technologies could result in a novel technique for Web development and content 10 | editing. 11 | {: .summary} 12 | 13 | Just today I found out that [the Chrome JS editor can 'hot-swap' your 14 | code][chrome-js]. It got me thinking of other systems I know of that allow you 15 | to edit code on-the-fly, breaking out of the tedious write/compile/test/run 16 | cycle. 17 | 18 | [chrome-js]: http://smotko.si/using-chrome-as-a-javascript-editor/ 19 | 20 | Smalltalk is an uncommon language these days, with the exception of its 21 | half-descendant, Objective-C. But whilst message passing, dynamic typing and 22 | object orientation are nice things, Objective-C missed the big, game-changing 23 | aspect of Smalltalk: image-based persistence, and the modeless editing 24 | environment which worked with it. 25 | 26 | Traditionally, applications get loaded into memory, and the operating system 27 | kicks off execution of the program starting with a well-known location (i.e. 28 | the start of the `main()` function). At this point you have to instantiate and 29 | initialize whatever objects you have manually, whether that's by accepting user 30 | input or reading data from the network or disk, *et cetera*. Your program gets 31 | into a fuzzy state between 'startup' and 'ready'. 32 | 33 | Smalltalk applications don't do that. Instead, you start with an image, load 34 | that directly into memory, and that already contains all the class definitions 35 | and object instances you need. The clever part is that to *change* a program 36 | you open this same image file in the VM and point-and-click through the 37 | definitions of various methods. When you've made the changes you need to, or 38 | perhaps just tweaked the appropriate objects, you save the image file back to 39 | disk, and it's ready to run again. If you want to start an application from 40 | scratch, you clone a blank image and fill it up with classes, methods and 41 | objects. 42 | 43 | Since this editing environment was typically part of the Smalltalk VM that ran 44 | the image, you wouldn't even need to cycle between editing and running your 45 | application: just open up the editor in the VM and live-edit the objects. 46 | 47 | * * * * * 48 | 49 | What does this mean for the Web? Well, the way I (and [Roy Fielding][rest]) see 50 | it, the browser is a stateful agent that just renders HTML to a viewport, and 51 | executes JavaScript. When you first load up your browser, it's a blank VM. 52 | You direct it to a URL, and HTML, CSS and JavaScript are fetched, which 53 | instruct the browser to render some kind of interface to the viewport. This 54 | interface is linked to the JS interpreter and the server through DOM events, 55 | with the most prominent and noticeable one being the default action of clicking 56 | an `` element. 57 | 58 | [rest]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm 59 | 60 | Those of us who first grokked the internet before the advent of JS-heavy web 61 | 'apps' still have this concept of navigating through pages. Now that 62 | conventions are moving towards 'single-page' web apps, the concept of a 'page' 63 | is losing its special meaning. In New World terms, a page is an artifact of a 64 | navigation event which completely trashes all but a small part of the VM (i.e. 65 | the cookie jar), replacing it with a new object tree and code definitions. Make 66 | sense? 67 | 68 | In a way, browsers have been capable of running web apps since the advent of 69 | `XMLHttpRequest`. The only difference is that today, the mean time between 70 | complete obliterations of that VM state is much longer. 71 | 72 | So if we're moving into a world of treating the browser as a VM, the manager of 73 | a long-lived application state, we need the other parts of the Smalltalk model: 74 | live-editing and persistence. The Chrome Web Inspector is great for modifying 75 | CSS rules on-the-fly (it's a declarative language, DOM redraws are cheap on a 76 | human timescale, go figure). Editing JS is trickier due to its functional, 77 | callback-based nature; the average JS object tree is much more nested than that 78 | of the average Smalltalk VM. But Chrome is again showing that it is possible. 79 | So the final piece of the puzzle is persistence: this bundle of HTML, 80 | JavaScript and CSS needs to be written *back* to the server. 81 | 82 | I have a hunch that WebDAV combined with standard HTTP authentication could be 83 | the answer. I'm not 100% sure on it, but I can easily envision a world where 84 | you fix bugs in your website by opening it up in a browser, reading a stack 85 | trace, fixing the JS in that same browser and persisting your changes 86 | back to the server. 87 | 88 | I dream of the days when the Web truly does resemble Smalltalk. 89 | 90 | * * * * * 91 | 92 | Voice your disagreement on [Hacker News](https://news.ycombinator.com/item?id=5198425). 93 | -------------------------------------------------------------------------------- /layouts/default.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html{html_attrs("en"), xmlns} 3 | %head 4 | %meta{'http-equiv' => "Content-Type", :content => "text/html; charset=utf-8"} 5 | %meta{:name => "viewport", :content => "width=device-width"} 6 | 7 | %meta{:property => "og:image:url", :content => "http://gravatar.com/avatar/fcd3a40babe606ef30cb342a6a74c54c.png"} 8 | %meta{:property => "og:image:secure_url", :content => "https://secure.gravatar.com/avatar/fcd3a40babe606ef30cb342a6a74c54c.png"} 9 | %meta{:property => "dc:creator", :content => "Zachary Voase"} 10 | %meta{:property => "dc:rights", :content => "Public Domain"} 11 | %meta{:property => "twitter:card", :content => "summary"} 12 | %meta{:property => "twitter:site", :content => "@zackwurst"} 13 | %meta{:property => "twitter:domain", :content => "zacharyvoase.com"} 14 | 15 | - if item[:kind] == "index" 16 | %meta{:name => "description", :content => "The Blog of Zachary Voase, brought to you in glorious HyperText."} 17 | %link{:rel => "alternate", :type => "application/rss+xml", :title => "Zack’s Blog", :href => "/rss.rdf"} 18 | %link{:rel => "alternate", :type => "application/rdf+xml", :title => "Zack’s Blog", :href => "/rss.rdf"} 19 | 20 | %link{ :rev => "foaf:weblog", :href => "http://zacharyvoase.com/"} 21 | 22 | %link{ :rel => "rdf:type", :href => "rss:channel"} 23 | %meta{:property => "dc:date", :content => sorted_articles.last[:created_at].to_s} 24 | %meta{:property => "rss:title", :content => "Zack’s Blog"} 25 | %link{ :rel => "rss:link", :href => "http://zacharyvoase.com/"} 26 | %link{ :rel => "rss:items", :href => "http://zacharyvoase.com/#articles"} 27 | 28 | %meta{:property => "twitter:title", :content => "Zack’s Blog"} 29 | %meta{:property => "twitter:url", :content => "http://zacharyvoase.com/"} 30 | %meta{:property => "twitter:description", :content => "The Blog of Zachary Voase, brought to you in glorious HyperText."} 31 | 32 | %meta{:property => "sy:updatePeriod", :content => "daily"} 33 | %meta{:property => "sy:updateFrequency", :content => "1"} 34 | %meta{:property => "sy:updateBase", :content => "2010-05-08T12:00+00:00"} 35 | 36 | - elsif item[:kind] == "article" 37 | %link{:rel => "rdf:type", :href => "rss:item"} 38 | %meta{:name => "description", :content => description_of(@item)} 39 | %meta{:property => "dc:date", :content => @item[:created_at].to_s, :datatype => "xs:date"} 40 | %meta{:property => "rss:title", :content => title_of(@item)} 41 | %meta{:property => "twitter:creator", :content => "@zackwurst"} 42 | %meta{:property => "twitter:url", :content => url_for(@item)} 43 | %meta{:property => "twitter:title", :content => title_of(@item)} 44 | %meta{:property => "twitter:description", :content => description_of(@item)} 45 | %link{:rel => "rss:link", :content => rel_url_for(@item)} 46 | 47 | %link{:rel => "stylesheet", :href => "/media/css/style.css", :media => "screen, projection"} 48 | 49 | %script{:type => "text/javascript", :src => "//use.typekit.net/jfx6whv.js"} 50 | %script{:type => "text/javascript"} 51 | :plain 52 | try{Typekit.load()}catch(e){} 53 | var _gaq = _gaq || []; 54 | _gaq.push(['_setAccount', 'UA-9915287-1']); 55 | _gaq.push(['_trackPageview']); 56 | (function() { 57 | var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 58 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 59 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga); 60 | })(); 61 | 62 | - if item[:kind] == "index" 63 | %title Zack’s Blog 64 | - else 65 | %title 66 | = title_of(@item) 67 | | Zack’s Blog 68 | 69 | %body{:id => @item[:id]} 70 | %div#header-container 71 | %div#header 72 | - if item[:kind] == "index" 73 | %h1{:property => "rss:description", :datatype => ""}< 74 | %span.prefix The Blog of 75 | Zachary Voase 76 | - else 77 | %h1< 78 | %a{:href => "/"}< 79 | %span.prefix The Blog of 80 | Zachary Voase 81 | 82 | = render('_about') 83 | = render('_spacer') 84 | 85 | - if @item[:kind] == 'article' 86 | = render('_article', :item => @item, :content => content) 87 | - elsif @item[:kind] == 'page' 88 | = render('_page', :item => @item, :content => content) 89 | - else 90 | = content 91 | 92 | %div#footer-container 93 | %div#footer 94 | - if item[:kind] == "article" 95 | %p.share-buttons 96 | = render('_tweet', :item => @item) 97 | %p.licensing 98 | All content on this site is released into the 99 | Public Domain. 100 | -------------------------------------------------------------------------------- /content/articles/2009/07/20/http-lock.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-07-20 4 | title: "Idea: Distributed HTTP Lock Server" 5 | --- 6 | 7 | I’d like some input on this idea: a HTTP server which acts as a distributed lock 8 | system, whereby: 9 | 10 | POST /lock//acquire/ 11 | 12 | would acquire a lock, and 13 | 14 | POST /lock//release/ 15 | 16 | would release it. If the lock doesn’t exist, it’s created. Locks could be 17 | ‘sharded’ across multiple processes or servers (although single locks would 18 | reside on a single process or server), and an attempt to acquire a lock that is 19 | in use would block (unless a timeout parameter is supplied). 20 | 21 | The basic problem which this would try to solve is dealing with a resource 22 | shared between multiple processes. By implementing it on top of HTTP, client 23 | libraries and tools could be written in practically any language or for any 24 | platform in existence. 25 | 26 | Here’s an example of where you’d use it: you’re running a very AJAX-intensive 27 | website. While the user might only have one browser window open, they’re still 28 | making multiple concurrent AJAX requests back to your server. On the 29 | server-side, you’re running multiple processes to handle these HTTP requests 30 | (this might be a Mongrel cluster, or perhaps a bunch of WSGI-handling Python 31 | processes). So as a request comes in, you don’t actually know which one of these 32 | processes the request will be handled by. Now let’s also propose that your AJAX 33 | views modify the request session in some way. So this is what happens if two 34 | requests come in at nearly the same time, one very shortly after the other: 35 | 36 | Request 1 | 37 | ------------------------+ 38 | POST /ajax/view1/ | 39 | loads session data | Request 2 40 | begins processing +------------------------- 41 | ... | POST /ajax/view2/ 42 | ... | loads session data 43 | ... | begins processing 44 | changes session data | ... 45 | saves session data | ... 46 | 200 OK (responds) | changes session data 47 | | saves session data 48 | | 200 OK (responds) 49 | 50 | By the end, only the changes `view2` made are actually present in the session, 51 | since it made those changes to a stale copy of the session data and clobbered 52 | all of `view1`’s changes. With locking, something else would happen: 53 | 54 | Request 1 | 55 | ------------------------+ 56 | POST /ajax/view1/ | 57 | ACQUIRE SESSION_KEY | 58 | loads session data | Request 2 59 | begins processing +--------------------------- 60 | ... | POST /ajax/view2/ 61 | ... | ACQUIRE SESSION_KEY 62 | ... | acquisition blocks 63 | ... | ... 64 | changes session data | ... 65 | saves session data | ... 66 | RELEASE SESSION_KEY ==> acquisition succeeds 67 | 200 OK (responds) | loads session data 68 | | begins processing 69 | | ... 70 | | ... 71 | | changes session data 72 | | saves session data 73 | | RELEASE SESSION_KEY 74 | | 200 OK (responds) 75 | 76 | In this case, by the time `view2` has loaded the session data, it has already 77 | been updated by `view1`, and so it doesn’t clobber the old data. This follows on 78 | to subsequent AJAX requests; other views attempting to change the session will 79 | have to wait until `view2` has finished doing so. 80 | 81 | That’s the general idea about locking. The system I’ve described, however, would 82 | make such behavior possible across the multiple processes and even machines 83 | which are so often used in web-serving environments. At the moment, most 84 | languages support in-process locks and filesystem locks. These are difficult if 85 | not impossible to distribute, since the first can only live within one process 86 | and the latter can only live within one machine (and even still may not be very 87 | cross-platform). 88 | 89 | I’m sure there are a bunch more use-cases, but this was the most obvious one 90 | which came to mind. 91 | 92 | The basic questions I’d like answered by the community (not because I’m lazy, 93 | but because I can’t speak for everyone) are: 94 | 95 | * Is this a good idea (i.e. worth spending the time/effort to implement)? 96 | 97 | * Are there already similar implementations available, preferably open-source? 98 | 99 | * Is there a feature you’d like to see implemented in such a system, whilst 100 | sticking to [D1T&DIW](http://en.wikipedia.org/wiki/Unix_philosophy)? 101 | 102 | * How might you go about implementing such a system? 103 | 104 | * Do you have good ideas for a name for such a system? 105 | -------------------------------------------------------------------------------- /content/articles/2009/11/06/capitalism.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-11-06 4 | title: "Capitalism, or Something Unlike It?" 5 | --- 6 | 7 | This is my response to an article in Forbes by Sramana Mitra, entitled 8 | “[Capitalism’s Fundamental Flaw](http://www.forbes.com/2009/11/05/innovation-ayn-rand-intelligent-technology-capitalism.html)”. 9 | 10 | It’s interesting that the author chooses the banking crisis as an example of the 11 | ‘fundamental flaw of capitalism’. No other industry on Earth is subject to as 12 | much tacit government intervention and taxation, through the government monopoly 13 | on the issue of currency, the institutionalized fraud of the fractional reserve 14 | system, and inflation. 15 | 16 | A (probably false) anecdote is brought to mind by this, actually. The bank 17 | robber [Willie Sutton](http://en.wikipedia.org/wiki/Willie_Sutton), when asked 18 | by a reporter why he robbed banks, reputedly replied “because that’s where the 19 | money is.” False or not, it seems fitting that governments would adopt a similar 20 | policy. And most importantly, with Willie as it is with governments, it’s not 21 | the bank’s money they take. It’s **yours**. 22 | 23 | It’s very easy for a government with a monopoly on a currency to begin an 24 | inflationary death spiral. The methods are many: fractional reserve banking (in 25 | any form), actual money printing, ‘quantitative easing’ or lowering of reserve 26 | requirements are all ways to ‘increase the money supply’. Once the inflation is 27 | set into motion, it is impossible to stop without incurring a catastrophic 28 | economic recession. Since politicians are elected in the short-term, they decide 29 | to continue with the short-term benefits (but long-term damages) of inflation. A 30 | quote from Macbeth seems unnervingly relevant: ”I am in blood stepp’d in so far 31 | that should I wade no more, Returning were as tedious as go o’er.” In his 32 | documentary series ‘Free to Choose’, Milton Friedman notes that inflation is 33 | comparable to alcoholism; to paraphrase, it’s fun in the beginning, bad later 34 | on, and increasingly difficult to give up ‘cold turkey’ the longer you continue. 35 | 36 | Many people I speak to today still don’t understand how currencies go into 37 | hyperinflation. The recent case of Zimbabwe illustrates this perfectly. It seems 38 | *insane* that a government could allow such rampant inflation to occur, doesn’t 39 | it? And yet the situation still continued to descend rapidly into chaos, until 40 | people gave up on Zimbabwe dollars and 41 | [started using gold instead](http://www.guardian.co.uk/world/video/2009/feb/11/zimbabwe-gold-panning-starvation-food). 42 | Of course, this is the nature of inflation. The easy credit made available 43 | through governmental economic policies in the U.S.A. and Europe have created a 44 | similar environment, albeit a [boiling 45 | frog](http://en.wikipedia.org/wiki/Boiling_frog) if ever there was one. 46 | 47 | So how does this relate to the article I was originally responding to? Well, the 48 | author’s main gripe is that the majority of capital in our so-called ‘free 49 | market economy’ (which, incidentally, hasn’t been free since 50 | [December 23, 1913](http://en.wikipedia.org/wiki/Federal_Reserve_Act)) seems to 51 | gravitate towards the speculators and market players rather than the innovators 52 | and creators of value. 53 | 54 | I can’t find much fault with her assessment, but I think the finger is being 55 | pointed in the wrong direction. Where do you think speculators get their initial 56 | investment capital from? It’s all **credit**. Speculators earn large amounts of 57 | money through leverage (i.e. borrowing large amounts of money to turn a good 58 | investment into a great one), and this leverage is provided by banks with a 59 | license to print money, thanks in no small part to the fractional reserve 60 | system. And of course, with the government monopoly on currency through legal 61 | tender laws, this basically has everyone *except* the banks and government bent 62 | over a financial barrel. 63 | 64 | The gist of my argument is that the speculator problem is completely a 65 | consequence of the government-controlled economy. The first instinct most people 66 | have is to blame capitalism for the recent economic issues, but a closer, 67 | rational inspection exposes who is really at fault here. 68 | 69 | ### Further Reading 70 | 71 | If you’re interested in further exploring some of the views I’ve shared in this 72 | post, see the following Wikipedia articles for more information: 73 | 74 | #### Individual Economists 75 | 76 | * [Murray Rothbard](http://en.wikipedia.org/wiki/Murray_Rothbard) 77 | * [Milton Friedman](http://en.wikipedia.org/wiki/Milton_Friedman) 78 | * [Friedrich Hayek](http://en.wikipedia.org/wiki/Friedrich_Hayek) 79 | * [Ludwig von Mises](http://en.wikipedia.org/wiki/Ludwig_von_Mises) 80 | * [Henry Hazlitt](http://en.wikipedia.org/wiki/Henry_Hazlitt) 81 | 82 | #### Economic Theory 83 | 84 | * [The Austrian School of Economics](http://en.wikipedia.org/wiki/Austrian_School_of_economics) 85 | (where ‘school’ == school of thought) 86 | * [Austrian Business Cycle Theory](http://en.wikipedia.org/wiki/Austrian_business_cycle_theory) 87 | * [Spontaneous Order](http://en.wikipedia.org/wiki/Spontaneous_order) 88 | -------------------------------------------------------------------------------- /content/media/sass/_layout.scss: -------------------------------------------------------------------------------- 1 | @import "compass/utilities/general/clearfix"; 2 | @import "_border-radius"; 3 | @import "_colors"; 4 | 5 | @mixin mobile { 6 | @media only screen and (max-width: 640px), 7 | only screen and (max-device-width: 640px) { 8 | @content; 9 | } 10 | } 11 | 12 | @mixin container { 13 | clear: both; 14 | float: left; 15 | margin: 0; 16 | padding: 0; 17 | width: 100%; 18 | } 19 | 20 | @mixin section { 21 | margin: 0 auto; 22 | width: 640px; 23 | 24 | @include mobile { 25 | width: 90%; 26 | padding-left: 5%; 27 | padding-right: 5%; 28 | } 29 | } 30 | 31 | hr { 32 | margin: 0 0 26px 0; 33 | border: none; 34 | border-top: 1px solid $grey * 2; 35 | } 36 | 37 | p, iframe, fieldset, table, pre { 38 | margin-bottom: 26px; 39 | } 40 | 41 | p.summary { 42 | padding-bottom: 26px; 43 | border-bottom: 3px double $grey * 2; 44 | } 45 | 46 | .contents { 47 | img { 48 | width: 640px; 49 | @include mobile { width: 100%; } 50 | } 51 | .center img { width: inherit; } 52 | } 53 | 54 | p.share-buttons { 55 | iframe { margin-bottom: 0; } 56 | } 57 | 58 | body, html { 59 | background-color: $teal; 60 | } 61 | 62 | #header-container { 63 | @include container; 64 | 65 | #header { 66 | @include section; 67 | 68 | @include mobile { 69 | width: 100%; 70 | padding-left: 0; 71 | padding-right: 0; 72 | } 73 | 74 | h1 { 75 | background-color: $light-teal; 76 | width: 75%; 77 | margin: 0; 78 | padding: 40px 12.5%; 79 | 80 | @include mobile { padding: 10px 12.5%; } 81 | 82 | .prefix { display: block; } 83 | } 84 | } 85 | } 86 | 87 | .content-container { 88 | @include container; 89 | 90 | background-color: #fff; 91 | border-top: 8px solid $teal; 92 | 93 | .content { 94 | @include section; 95 | 96 | padding: 20px 0 0 0; 97 | } 98 | 99 | @include mobile { 100 | #index & div.content { padding-top: 10px; } 101 | } 102 | } 103 | 104 | #footer-container { 105 | @include container; 106 | 107 | border-top: 8px solid $teal; 108 | padding-bottom: 20px; 109 | @include mobile { 110 | padding-bottom: 0; 111 | } 112 | 113 | #footer { 114 | @include section; 115 | @include border-radius-bottom(10px); 116 | @include mobile { 117 | @include border-radius-bottom(0); 118 | } 119 | 120 | background-color: $light-teal; 121 | height: 100px - (20px * 2); 122 | padding: 20px; 123 | width: 640px - (20px * 2); 124 | 125 | p { 126 | margin-bottom: 0; 127 | } 128 | } 129 | } 130 | 131 | .spacer { 132 | @include container; 133 | 134 | border-top: 8px solid $teal; 135 | 136 | .spacer-child { 137 | @include section; 138 | height: 40px; 139 | background-color: $light-teal; 140 | } 141 | } 142 | 143 | #links { 144 | @include pie-clearfix; 145 | 146 | margin-bottom: 20px; 147 | 148 | ul { 149 | margin: 0; 150 | padding: 0; 151 | } 152 | 153 | li { 154 | display: inline; 155 | margin: 0; 156 | padding: 0; 157 | margin-left: 8px; 158 | 159 | &:before { 160 | content: "★"; 161 | margin-right: 8px; 162 | } 163 | 164 | &.first { 165 | margin-left: 0; 166 | &:before { content: ''; } 167 | } 168 | 169 | a { white-space: nowrap; } 170 | } 171 | } 172 | 173 | #articles { 174 | @include clearfix; 175 | 176 | margin: 10px 0 20px 0; 177 | 178 | li { 179 | display: block; 180 | list-style: none; 181 | margin-bottom: 10px; 182 | 183 | .date { 184 | clear: both; 185 | display: block; 186 | float: right; 187 | width: 100px; 188 | } 189 | 190 | .link { white-space: nowrap; } 191 | } 192 | 193 | @include mobile { 194 | margin-top: 0; 195 | margin-bottom: 10px; 196 | 197 | li { 198 | margin-bottom: 0; 199 | padding-bottom: 10px; 200 | padding-top: 10px; 201 | &:first-child { padding-top: 0; } 202 | border-bottom: 1px solid #AAA; 203 | &:last-child { border-bottom: none; } 204 | 205 | .date { float: none; } 206 | .link { white-space: normal; } 207 | } 208 | } 209 | } 210 | 211 | p.index-link-container { margin-bottom: 10px; } 212 | 213 | p.center { text-align: center; } 214 | 215 | h1 { margin: 8px 0; } 216 | 217 | div.content.page h1, p.date { 218 | border-bottom: 3px double $grey * 2; 219 | padding-bottom: 16px; 220 | margin-bottom: 26px; 221 | } 222 | 223 | h4 { margin-bottom: 16px; } 224 | 225 | blockquote, pre { 226 | @include border-radius(5px); 227 | 228 | background-color: $grey * 2; 229 | padding: 10px; 230 | float: none; 231 | overflow-x: scroll; 232 | } 233 | 234 | blockquote { 235 | margin: 0 0 16px 0; 236 | padding: 10px 20px; 237 | min-width: 640px - (20px * 2); 238 | 239 | @include mobile { min-width: 0; } 240 | 241 | & :last-child { margin-bottom: 0 !important; } 242 | } 243 | 244 | ul, ol { margin-right: 0; } 245 | 246 | dl { 247 | margin: 0 0 16px 0; 248 | 249 | dd { margin-bottom: 8px; } 250 | } 251 | 252 | ins, del { 253 | @include border-radius(5px); 254 | 255 | background-color: $teal; 256 | display: block; 257 | padding: 10px; 258 | width: 640px - (10px * 2); 259 | @include mobile { width: auto; } 260 | } 261 | 262 | p, ins, del, pre, h1, h2, h3, h4, h5, h6 { clear: both; } 263 | -------------------------------------------------------------------------------- /content/articles/2009/09/10/django-settings-flavours.md: -------------------------------------------------------------------------------- 1 | --- 2 | kind: article 3 | created_at: 2009-09-10 4 | title: "Django Settings Flavours" 5 | --- 6 | 7 | Anyone who’s ever deployed a Django site will have encountered a common problem. 8 | You’ll usually have both a development and a production environment in which 9 | you’re running your Django project; many large-scale deployments will also have 10 | a staging environment. How do you maintain separate configurations for each of 11 | these environments, using each configuration in its appropriate environment? 12 | 13 | In my Django projects, I have a setup, refined over a period of several months, 14 | which I feel provides a very neat, elegant and flexible solution to this 15 | problem. It leverages the fact that the default Django project layout, as 16 | generated by `django-admin startproject`, assumes very little about the 17 | individual development process or deployment architecture which the project will 18 | have as it matures. While this can be intimidating for newcomers, it is a design 19 | decision which allows seasoned Djangonauts to stretch the boundaries of the 20 | framework, taking advantage of the power and flexibility provided. 21 | 22 | ### Getting Started 23 | 24 | I begin with the default project layout. Running 25 | `django-admin startproject myproject` will give this: 26 | 27 | myproject/ 28 | |-- __init__.py 29 | |-- manage.py 30 | |-- settings.py 31 | `-- urls.py 32 | 33 | I then break `settings.py` into a directory, like so: 34 | 35 | myproject/ 36 | |-- __init__.py 37 | |-- manage.py 38 | |-- settings 39 | | |-- __init__.py 40 | | |-- common.py 41 | | |-- development.py 42 | | `-- production.py 43 | `-- urls.py 44 | 45 | There are a few things to note about the `settings` directory and its contents: 46 | 47 | * It contains an `__init__.py` file, which makes it a Python package. 48 | 49 | * `common.py` contains the settings which are common to all environments. This 50 | includes stuff like `ROOT_URLCONF`, `INSTALLED_APPS`, `USE_I18N`, context 51 | processors, middleware and so on. It also includes a function called 52 | `_merge()`, which I’ll get to later. 53 | 54 | * There are modules for each deployment environment. These contain database 55 | settings, `DEBUG` and `TEMPLATE_DEBUG`, `CACHE_BACKEND`, et cetera. 56 | 57 | In order to use a particular settings ‘flavour’, simply set the 58 | `DJANGO_SETTINGS_MODULE` environment variable in the context where you’ll be 59 | running your application. For example, it might be 60 | `myproject.settings.development` for your development environment, and 61 | `myproject.settings.production` in production. 62 | 63 | ### The `_merge()` function 64 | 65 | The only issue with this solution, as it stands, is how to get the settings from 66 | `common.py` into the environment-specific modules. You could try 67 | `from myproject.settings.common import *`, but that ties you into an absolute 68 | import. Instead, add the following function to the bottom of `common.py`: 69 | 70 | #!python 71 | def _merge(local_vars): 72 | local_vars.update((k, v) for k, v in globals().items() if k[0] != '_') 73 | 74 | And at the top of `development.py` (or `production.py`, et cetera): 75 | 76 | #!python 77 | from . import common; common._merge(vars()) 78 | 79 | This is essentially a workaround for the fact that relative imports cannot use 80 | `import *`. When `_merge()` calls `globals()`, it gets the dictionary of the 81 | `common.py` namespace, with each variable in the namespace being represented by 82 | a `key => value` pair in the dictionary. At the module level in 83 | `development.py`, `vars()` returns this same dictionary, but for that module 84 | instead. Where it really helps is that both of these dictionaries support 85 | assignment; that is, `vars()['foo'] = "bar"` is perfectly valid and will assign 86 | `"bar"` to the variable `foo`. Hence, `_merge()` now has dictionaries for both 87 | the `common.py` and `development.py` namespaces, and it simply copies over all 88 | the public variables from `common.py` (i.e. those not prefixed with an 89 | underscore) to `development.py`. 90 | 91 | It’s a concept best demonstrated by example. Let’s say `common.py` contained the 92 | following: 93 | 94 | #!python 95 | A = 1 96 | B = 2 97 | 98 | def _merge(local_vars): 99 | local_vars.update((k, v) for k, v in globals().items() if k[0] != '_') 100 | 101 | And `development.py` contained this: 102 | 103 | #!python 104 | from . import common; common._merge(vars()) 105 | 106 | C = 3 107 | print (A, B, C) 108 | 109 | Running `python -m settings.development` from the project root will print 110 | `(1, 2, 3)`. The variables `A` and `B` have been copied over into the 111 | `development.py` namespace for you to use as you wish. 112 | 113 | ### Applying the Solution 114 | 115 | All it takes is writing up your `common.py` followed by your 116 | environment-specific modules, and you’re done. You can set the 117 | `DJANGO_SETTINGS_MODULE` environment variable from an `application.wsgi` file, a 118 | virtualenv `activate` script, a web server configuration, or on the command line 119 | (if you’re using `./manage.py`). 120 | 121 | For your information, I used the UNIX 122 | [`tree` command](http://mama.indstate.edu/users/ice/tree/) to generate the file 123 | listings for the example project. 124 | -------------------------------------------------------------------------------- /content/articles/2010/11/11/sockets-and-nodes-i.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2010-11-11 3 | kind: article 4 | title: "Sockets and Nodes—An Experiment, Part I" 5 | --- 6 | 7 | In anticipation of [Full Frontal][ff] tomorrow, I decided to play around with 8 | two hot new tools, [node.js][] and [socket.io][], in putting together a useful 9 | ‘toy’ application. Over a few blog posts, I’ll demonstrate how to build a 10 | lightweight app to track live Twitter feeds, starting with something incredibly 11 | basic and adding features incrementally. 12 | 13 | [ff]: http://2010.full-frontal.org 14 | [node.js]: http://nodejs.org/ 15 | [socket.io]: http://socket.io/ 16 | 17 | 18 | ## Requirements 19 | 20 | Start by installing [node.js][], `npm` (the node package manager), and a 21 | couple of JS libraries. Using [homebrew][]: 22 | 23 | [homebrew]: http://mxcl.github.com/homebrew/ 24 | 25 | #!bash 26 | brew install node npm 27 | # Add NODE_PATH to your shell environment and config at the same time! 28 | `echo 'export NODE_PATH=/usr/local/lib/node' | tee -a ~/.zsh_profile` 29 | 30 | npm install socket.io 31 | npm install twitter-node 32 | 33 | Installation instructions for other systems may be found on the internet. I 34 | have faith in your Googling abilities. 35 | 36 | 37 | ## Client-side 38 | 39 | Version zero’s client will consist of a single HTML file; start with a basic 40 | skeleton in a file called `index.html`: 41 | 42 | #!html 43 | 44 | 45 | 46 | BIEBER!!!!1! 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 |
    61 | 62 |
63 | 64 | 65 | 66 | Now for the client JavaScript (which you should add to the empty `