<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" xml:lang="en"><title type="text">Stoneship</title><link rel="alternate" type="text/html" href="http://pipes.yahoo.com/pipes/pipe.info?_id=kiNXeKvi2xGLqSmcnkartA" /><subtitle type="html">Combines the stoneship.org blog feed and sidebar links. Linksare prefixed by "[link]".</subtitle><updated>1970-01-01T00:00:00+00:00</updated><generator>http://pipes.yahoo.com/pipes/</generator><link rel="license" type="text/html" href="http://creativecommons.org/licenses/by/3.0/" /><link rel="self" href="http://feeds.feedburner.com/stoneship" type="application/atom+xml" /><feedburner:browserFriendly>This is an XML content feed. It is intended to be viewed in a newsreader or syndicated to another site, subject to copyright and fair use.</feedburner:browserFriendly><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry><title type="text">How nanoc's Rules DSL Works</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/bjAUuDUBxVM/" /><updated>2009-10-14T06:00:00-07:00</updated><id>tag:stoneship.org,2009-10-14:/journal/2009/how-nanocs-rules-dsl-works/</id><content type="html">&lt;p&gt;nanoc has recently gained an entirely new way of specifying how pages should be processed. Instead of describing processing instructions in YAML, they are now described using pure Ruby, like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;compile '/articles/*/' do filter :erb filter :markdown layout 'article' filter :rubypants
end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;These rules are located in a file called &lt;em&gt;Rules&lt;/em&gt;, which is loaded by nanoc on startup. This blog article explains how these rules are implemented so that you can use the same idea in your projects.&lt;/p&gt; &lt;p&gt;There are two slightly different ways of implementing such a DSL, and I'll describe them both in this post. The first iteration of nanoc used a DSL based on method #1, but in the second iteration I switched to the cleaner, prettier method #2.&lt;/p&gt; &lt;p&gt;For demonstration purposes, this blog post will not use nanoc's domain-specific language, but instead use a much more minimal language that uses a &lt;code&gt;#process&lt;/code&gt; method to define rules. The Rules file in the example will look like this: &lt;pre&gt;&lt;code&gt;process /oo/ do puts "I am rule /oo/ and I am processing #{item.inspect}!"
end&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;Method #1&lt;/h2&gt; &lt;p&gt;This method uses a DSL that looks slightly different: it has an explicit &lt;code&gt;item&lt;/code&gt; parameter, and all methods are called explicitly on this item. A Rules file written using method #1 looks like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;process /oo/ do |item| puts "I am rule /oo/ and I am processing #{item.inspect}!"
end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The &lt;code&gt;#process&lt;/code&gt; function creates a &lt;em&gt;rule&lt;/em&gt; (I'll show you how in a bit) which can be applied to an item. The first function argument is a pattern that specifies which items this rule can be applied to. When nanoc loads the Rules file, it creates a &lt;code&gt;Rule&lt;/code&gt; instance containing this pattern as well as a block containing the actual code. Something like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class Rule def initialize(pattern, block) @pattern = pattern @block = block end def applicable_to?(item) item.identifier =~ @pattern end def apply_to(item) @block.call(item) end end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The &lt;code&gt;#applicable_to?&lt;/code&gt; method is used for determining whether the rule is able to process a given item, and the &lt;code&gt;#apply_to&lt;/code&gt; method performs the actual processing by calling the block and giving the item as a parameter.&lt;/p&gt; &lt;p&gt;The Item class is, in this case, rather simple: it only contains an &lt;code&gt;identifier&lt;/code&gt; attribute. It is implemented like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class Item attr_reader :identifier def initialize(identifier) @identifier = identifier end end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Here's how the rules file is loaded and parsed:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class App def initialize @rules = [] end def load_rules rules_content = File.read('Rules') dsl = DSL.new(@rules) dsl.instance_eval(rules_content) end end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The &lt;code&gt;#instance_eval&lt;/code&gt; method takes a block or a string and evaluates it in the content of the object on which the method is called. The last line of the code example above evaluates the string, which contains the content of the rules file, in the context of a &lt;code&gt;DSL&lt;/code&gt; instance.&lt;/p&gt; &lt;p&gt;The idea is to have the &lt;code&gt;#process&lt;/code&gt; method implemented in this &lt;code&gt;DSL&lt;/code&gt; instance. This method then generates a &lt;code&gt;Rule&lt;/code&gt; instance using the pattern and the block given to the &lt;code&gt;#process&lt;/code&gt; call, like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class DSL def initialize(rules) @rules = rules end def process(pattern, &amp;amp;block) @rules &amp;lt;&amp;lt; Rule.new(pattern, block) end end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The &lt;code&gt;rules&lt;/code&gt; array contains all rules. To actually use these rules, i.e. find a rule for an item and apply it, you'd use something like this (no error checking implemented):&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class App def process(item) rule = rules.find { |r| r.applicable_to?(item) } rule.apply_to(item) end end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;And that's how the DSL is implemented using method #1.&lt;/p&gt; &lt;h2&gt;Method #2&lt;/h2&gt; &lt;p&gt;The second method gets rid of the extra block parameter to the &lt;code&gt;#process&lt;/code&gt; call. Using method #2, the Rules file looks like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;process /oo/ do puts "I am rule /oo/ and I am processing #{item.inspect}!"
end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;To get rid of the extra &lt;code&gt;item&lt;/code&gt; parameter, the block will have to be evaluated in the context of an object that provides access to the item in a different way. One way of doing this is to provide an &lt;code&gt;@item&lt;/code&gt; variable in a &lt;code&gt;RuleContext&lt;/code&gt; object, which looks like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class RuleContext def initialize(item) @item = item end end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Applying a rule to an item no longer involves calling the block and giving the item as a parameter. Instead, a &lt;code&gt;RuleContext&lt;/code&gt; is created with a given item, and then the block is evaluated in the context of this rule context, like this:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class Rule def apply_to(item) rule_context = RuleContext.new(item) rule_context.instance_eval(&amp;amp;@block) end end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Accessing the item is now done using &lt;code&gt;@item&lt;/code&gt;. If you want to be able to use &lt;code&gt;item&lt;/code&gt; without that &lt;code&gt;@&lt;/code&gt; sigil, define an accessor:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;class RuleContext attr_reader :item end&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;And that's how method #2 works.&lt;/p&gt; &lt;p&gt;You can get the source of the example implementation &lt;a rel="nofollow" target="_blank" href="http://stoneship.org/pub/software/how-nanocs-rules-dsl-works.tar.bz2"&gt;over here&lt;/a&gt;. It contains two directories; one for each method. Each directory contains a dsl.rb file which contains the library code, a test.rb file which can be executed, and of course a Rules file which contains the rules.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/bjAUuDUBxVM" height="1" width="1"/&gt;</content><feedburner:origLink>http://stoneship.org/journal/2009/how-nanocs-rules-dsl-works/</feedburner:origLink></entry><entry><title type="text">[link] Why "require 'rubygems'" Is Wrong</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/Rv9XX-CnUok/require-rubygems-antipattern" /><updated>2009-09-15T10:02:27-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_982c88ab7bb410023b2b279f32e9ad83</id><content type="html">Exactly.&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/Rv9XX-CnUok" height="1" width="1"/&gt;</content><feedburner:origLink>http://tomayko.com/writings/require-rubygems-antipattern</feedburner:origLink></entry><entry><title type="text">[link] Railtime - Treinen geopositie</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/PSPQocAvo1c/trainpositions.aspx" /><updated>2009-08-07T05:57:15-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_57031f8ceb98bd2da80013f0ac8574ac</id><content type="html">Real-time map of all trains in Belgium—nice!&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/PSPQocAvo1c" height="1" width="1"/&gt;</content><feedburner:origLink>http://www.railtime.be/website/trainpositions.aspx?lang=NL&amp;type=i</feedburner:origLink></entry><entry><title type="text">[link] A textile editor, built over HTML5 canvas</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/4rhYoG1hnAs/" /><updated>2009-08-07T01:12:35-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_1ebb2099f70ade4f300daf8c62f7bcc5</id><content type="html">Nice use of HTML5 canvas. This could be quite useful.&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/4rhYoG1hnAs" height="1" width="1"/&gt;</content><feedburner:origLink>http://guillaume.bort.fr/textile-editor/</feedburner:origLink></entry><entry><title type="text">[link] Tim Burton's Alice In Wonderland trailer</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/jjmFuJov6nc/watch" /><updated>2009-07-27T11:55:14-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_c878cae42c4c17ef0909c5fed2754872</id><content type="html">Looks pretty damn awesome.&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/jjmFuJov6nc" height="1" width="1"/&gt;</content><feedburner:origLink>http://www.youtube.com/watch?v=LjMkNrX60mA</feedburner:origLink></entry><entry><title type="text">[link] Creative Sense - New icons for the Adobe Creative Suite for Mac OS X and Windows</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/yPeaNVMn9os/" /><updated>2009-07-21T01:20:02-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_065d1adfaccd383fc3af42c82c31e24d</id><content type="html">A rather nice set of replacement icons for the Adobe Creative Suit.&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/yPeaNVMn9os" height="1" width="1"/&gt;</content><feedburner:origLink>http://beta.dthought.net/icon-sets/creative-sense/</feedburner:origLink></entry><entry><title type="text">[link] Living with First-Person Shooter Disease</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/wURROg-n8V4/watch" /><updated>2009-07-14T01:02:38-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_db1299c99d0251a62fdf8c8c9adde078</id><content type="html">Title says it all!&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/wURROg-n8V4" height="1" width="1"/&gt;</content><feedburner:origLink>http://www.youtube.com/watch?v=-jBKKV2V8eU</feedburner:origLink></entry><entry><title type="text">[link] Max Payne 3</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/A6CO5iiBfdQ/index2.php" /><updated>2009-07-11T11:23:35-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_6b461e8eeb026a1e3a200071a53f6c8b</id><content type="html">Whoa, what the fuck is Rockstar thinking?&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/A6CO5iiBfdQ" height="1" width="1"/&gt;</content><feedburner:origLink>http://www.rockstargames.com/maxpayne3/index2.php</feedburner:origLink></entry><entry><title type="text">[link] SlickMap CSS — A Visual Sitemapping Tool for Web Developers</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/zStDfZkSvPA/" /><updated>2009-07-09T01:20:45-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_dcd9e10aabec8d955a2fadd4c3552f3e</id><content type="html">Uses CSS to style unordered lists in HTML to end up with a pretty sweet visual site map. Quite nicely done.&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/zStDfZkSvPA" height="1" width="1"/&gt;</content><feedburner:origLink>http://astuteo.com/slickmap/</feedburner:origLink></entry><entry><title type="text">[link] An Unofficial Q&amp;A about the Discontinuation of the XHTML2 WG</title><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/stoneship/~3/PcDyNiF0ZSM/" /><updated>2009-07-07T02:25:25-07:00</updated><id>kiNXeKvi2xGLqSmcnkartA_6f3ddeb1b66e6cf2b10815ca3238ab13</id><content type="html">This should hopefully clear up some of the confusion around HTML4, XHTML1, XHTML2, HTML5 and XHTML5.&lt;img src="http://feeds.feedburner.com/~r/stoneship/~4/PcDyNiF0ZSM" height="1" width="1"/&gt;</content><feedburner:origLink>http://hsivonen.iki.fi/xhtml2-html5-q-and-a/</feedburner:origLink></entry></feed>
