plexus/hexp — pull request #3 by plexus (2013-06-26 20:03:31+00:00)
Title: Experiment with chainable selectors and rewriters

This commit introduces a first attempt at coming up with a chainable interface
for describing transformations of Hexp trees.

## Hexp::Node::Selector

The idea is to have certain methods that select certain nodes in a tree. At
this point there is one such method : select, which takes a block that receives
a node and returns a boolean.

```ruby
node.select {|child_node| child_node.class? 'strong' }
```

The result is a Hexp::Node::Selector, it is Enumerable, so one can do #to_a, #map,
etc and to loop over the nodes in the tree that have class="strong".

## Hexp::Node::Rewriter

This can be combined with the concept of "rewriting" a tree, here the block receives
a node, and the return value of the block takes the place of the original node in the
new, rewritten tree.

This return value can be a single node or an array of nodes (zero or more). Nil is
treated special in that it is consider a no-op. When the block returns nil, the
original node is simply kept in the tree unaltered.

This example will give every

the class "para".

```ruby
node.rewrite do |child_node|
if child_node.tag == :p
child_node.attr('class', 'para')}
end
end
```
## Combined

Nodes and Selectors both respond to #rewrite, in the latter case only the nodes
that match the selection criterium are passed to the block, so the former example
can be rewritten as

```ruby
node.select {|child| child.tag == :p}
.rewrite {|child| child_node.attr('class', 'para') }
```

The result is an instance of Hexp::Node::Rewriter, which lazily evaluates the
transformation when converted to a Hexp. It includes Hexp::DSL, so it can be
used as any Hexp::Node (tag, attributes, to_html, etc).

These two can form the basic building blocks for a richer API. As a tryout they
already implement #wrap and #attr, so to take every paragraph, give it the class
'para', and wrap it in a div with class 'wrapper' you can write

```ruby
node.select {|child| child.tag == :p}
.attr('class', 'para')
.wrap(:div, 'class' => 'wrapper')
```

## The future

The next step will be to leverage SASS's CSS parser to implement a CSS selector,
then the above would become

```ruby
node.css('p')
.attr('class', 'para')
.wrap(:div, 'class' => 'wrapper')
```

## tl;dr drinks for feedback, I mean it

API design is hard, so I'm desperately looking for feedback on this. I will
personally buy you a beer (or other beverage of your choosing), at Eurucamp,
Arrrrcamp, any time in Berlin or at the first occasion that presents itself,
for every constructive comment on this PR.