RubyConf 2007 First Day Afternoon

November 2nd, 2007

Nathaniel Talbott: Why Camping Matters

“I don’t know about you, but I am totally psyched about this conference!” Nathaniel has spoken at every RubyConf.

 bacon, egg & cheese biscuit, west egg cafe

Photo by laurafries.com

Every talk needs a metaphor, and this talk’s will be the bacon, egg, and cheese biscuit.

Bacon

The bacon is the connection to the creator, and to chunky bacon. It’s a 4k micro framework created by _why.

A whole Camping app goes in a single file and is a typical MVC.

Camping.goes :Blog

module Blog::Controllers
  class Index < R '/' # route to /
    def get
      "Hello Rubyconf!"
    end
  end
end

...
$ camping blog.rb
# blah blah, up on localhost:3301

Hooray, Hello World!. Let’s start doing more MVC:

Camping.goes :Blog

module Blog::Controllers
  class Index < R '/' # route to /
    def get
      render :index
    end
  end
  class Add < R '/add'
    render :add
  end
end

module Blog::Views
  def index
    # this is markaby
    a "add post", :href => R(Add)
  end
  def add
    form :method => :post do
      fieldset do
        label "Title: "
        input :name => :title; br
        label "Body"
        textarea :name => :body; br
      end
    end
  end
end

Camping is nice when making a sketch of the app, and eliminates all the unnecessary crap. To that extent, it’s wonderful for rapid prototyping. We’ve already got most of a Blog skeleton. Now, time for M of MVC.

Camping.goes :Blog

module Blog::Controllers
  class Index < R '/' # route to /
    def get
      render :index
    end
  end
  class Add < R '/add'
    def get
      render :add
    end
    def post
      Post.create!(@input)
      redirect Index
    end
  end
end

module Blog::Views
  def index
    # this is markaby
    a "add post", :href => R(Add)
  end
  def add
    form :method => :post do
      fieldset do
        label "Title: "
        input :name => :title; br
        label "Body"
        textarea :name => :body; br
      end
    end
  end
end  

# This is ActiveRecord under the covers, that's _not_ part of the 4k
module Blog::Models
  class Post < Base; end 

  # Migrations go inline
  class CreatePost < V 1
    def self.up
      create_table :blog_posts do |t|
        t.column :title, :string
        t.column :body, :text
      end
    end
  end
end

def Blog.create;

Cool, we’ve got something working. It’d be nice if we knew what was there…

module Blog::Views
  def index
    @posts.each do |e|
      h2{e.title}
      p e.body
      hr
    end

    # this is markaby
    a "add post", :href => R(Add)
  end
end

Permalinks:

class View < R '/view/(\d+)'
  def get(id)
    @post = Post.find(id)
    render :view
  end
end  

[As you can see, this all happened very fast, as we're only 17 minutes in so far.] Nathaniel adds comments in the next 4 minutes.

Egg

So, that’s really most of Camping. To keep the metaphor going, we need to break some eggs (over Rails). Some differences between Rails & Camping:

  • Convention over configuration vs. minimalizism (nothing to configure)
  • Opinionated vs. wackyness (“Camping isn’t so much opinionated as it is… strange.”)
  • Boring vs. different
  • Just like always, but better vs. “More flexible than a wet noodle”
  • Lots and lots of helpers vs. diy
  • Rails vs. Ruby
  • Daunting to hack on vs. hackable (if odd)
  • Encourages conformance vs. encourages experimentation

Cheese

Actually hacking on stuff is fun. That’s why Camping & any other cool, out-there framework or language is there: “we need to feed our inner hacker!” Keys to feeding your inner hacker:

  • Needs to be regular (daily would really be great)
  • Eat a variety of foods (different is good: Rails Monday, Camping Tuesday)
  • You have to feed on things that you’re passionate about

Biscuit

The biscuit is the Community. Why do people who have been to both RailsConf and RubyConf prefer RubyConf? [A show of hands confirmed this.] RubyConf is still small, and it’s the hobbyist conference. Both of those are not true of RailsConf. We, the Ruby community, need both conferences, with RailsConf bringing the momentum and RubyConf bringing the vitality.

Questions

Testing: There’s a testing framework for Camping called Mosquito.

Production: Nathaniel does use it in production, but not for clients.
Intranet apps would be great.

Nathan Sobo: Treetop: Bringing the Elegance of Ruby to Syntactic
Analysis

Earlier in the day, a significant but small number of folks raised their hands when asked if they had ever written a parser. However, many of us have written ad-hoc parsers on other stuff (in regexes, usually). Why don’t regular programmers use the same tools as language designers for creating parsers? Because the tools for making parsers have a really high barrier to entry, even though regexes and loops make for brittle software. Hopefully, some new tools will lower this barrier to entry. Treetop is an attempt at this.

What is a Context Free (Generative) Grammar? A grammar is a program, a program that generates every possible string in a language. However, there’s a problem with this model of grammars, as sometimes there’s ambiguity (think of if/else with ambigous nesting). Instead, do Parsing Expression Grammars [PEG], which Treetop uses, and work on recognizing language rather than generating it.

PEGs are just a generalization of ur-regexes, but are more powerful because they can do recursion. Here’s something for (((a))):

# Treetop, not Ruby
grammar ParenLanguage
  rule nested_parens
    '(' nested_parens ')' / [a-z]
  end
end  

# use in Ruby like so
load_grammar 'paren_language'
parser = ParenLanguageParser.new
tree = parser.parse("(((a)))")

The tree above is an OO view of the parse.

Some livecoding

Let’s parse the language of arithmetic.

(5 + 2) * (10 - 5)

First, draw a tree over the thing you want to parse.

Here’s what I captured from what he livecode:

dir = File.dirname(__FILE__)
require "#{dir}/test_header"

load_grammar "#{dir}/aritmetic"

class ArithmeticGrammarTest < Test::Unit::TestCase
  include GrammarTestHelper

  def setup
    @parser = ArithmeticParser.new
  end
  def test_numbers_simple
    assert @parser.parse('0').success?
    assert_equal 0, @parser.parse('0').eval
    assert @parser.parse('123').success?
  end

  def test_numbers_use_helpers
    assert 0, parse('0').eval
  end

  def test_variables
    assert_equal 2, ... something ..
  end

  test_multiplicative
    assert_equal 20,  parse('x * 10').eval({'x' => 2})
    assert_equal 4 * 3 * 2, parse('4 * 3 * 2 * 1').eval...
  end

  def test_additive
    assert_equal 5 + 2 * 10 - 5, parse('5 + x * 10 - y').eval({'x' => 2, 'y' => 5})
  end 

  def test_parentheses
    assert_equal (5 + 2) * (10 - 5), parse('(5 + x) * (10 - y)').eval({'x' => 2, 'y' => 5})
  end
end

# different file
grammar Arithmetic
  rule primary
    variable
    /
    number
    /
    '(' space additive space ')' {
      eval(...)
    }
  end  

  rule space
    ' '*
  end  

  rule additive
    operand_1:multiplicative space additive_op space operand_2:additive {
      def eval(env)
        additive_op.apply(operand_1.eval(env), operand_2.eval(env))
      end
    }
    /
    primary
  end

  rule additive_op
    '+'
  end

  rule multiplicative
    operand_1:primary space '*' space operand_2:multiplicative {
      def eval(env)
        operand_1.eval(env) * operand_2.eval(env)
      end
    }
    /
    primary
  end

  rule variable
    [a-z]+ {
      def eval(env)
        env[name]
      end
      def name
        text_value
      end
    }
  end  

  rule number
    ([1-9] [0-9]* / '0') {
      def eval(env)
        text_value.to_i
      end
    }
  end
end

Note that we didn’t lex anywhere above, and that the stuff above is composable. Grammars can be opened up an have other Grammars included (include Arithmetic, then override some part of it!)).

[Just showed up a Turing-complete Lambda Calculus language parser in 132 lines]

Imagine (using each others PEGs):

grammar RubyWithSQLStrings
  include Ruby
  include SQL

  rule expression
    ruby_expression
  end  

  rule ruby_string
    quote sql_expression quote / super
  end
end 

We didn’t cover lookahead, but there’s both negative and positive. Here’s negative (a quote, a bunch of not quotes, followed by quote):

'"' (!'"' .)* '"'

Memoization makes all of this stuff work, although it wasn’t an option in the past.

Ryan Davis: Hurting Code for Fun and Profit

On Ruby Sadism, Asceticism, & Introspection.

Start with a story: Once upon a time, a developer went to a New Place. The New Place had legacy code (any code that you didn’t write yourself). Every piece of legacy code reference 5 other files and everything is a rats nest. The developer is mad. He does what is “right” and kills all of the people responsible.

OR: Developer finds the dependencies, the rats nest, and gets angry. But, this time he pulls out tools and instead of maiming the people, he hurts the code. He shows the code who is boss.

 People will press charges if you hurt them.

Photo by candescence

People will press charges when you hurt them, code won’t.

Why Hurt Code

Hurting code is fun, and may make your code cleaner, more readable, and easier to test. If you make fixing code fun, you’ll do it much more often. An obvious example of sadism is killing a bug by writing a new test.

For some reason, people love complexity. Asceticism is characterized by strict self-discipline. Test-first is an example of asceticism. YAGNI is an example of abstenstion. Resist indulgence! (needless complexity, overly-clever code, code that you don’t need right now, “technical debt”)

“A developer’s obligation is to make sure that the code as written makes the clearest possible statement as to how the solution was understood at the time of writing.” –Ward Cunningham

Introspection-oriented development

How to do it?

  • Ask yourself constantly: How do I do better? How did I overlook that bug? Am I wrong?
  • Improve yourself: read 1 nerd book per month (which is 12x industry average)
  • c2.com & other wikis with smart people
  • Get rid of high-flow mailing lists, meaningless blogs in feedreader, bad sites
  • Grow: Learn a language a year, learn your tools much better, examine your habits, study something wierd
  • Push yourself: Write lots; throw away; write more (they weren’t kidding when they said “practice makes perfect”)
  • Push yourself more: Be competetive, challenge the status quo
  • Feel: Have an opinion, have passion (zentest, flog & heckle all came from love) (image_science came from hate)
  • Feedback: Figure out how to get better

Tools

Flog can help find code that will be hard to test and understand.

Coverage tools are good at finding gaping holes, but not anything about quality.

Heckle (“the most sadistic tools I’ve written”) mutates your implementation to make sure that your tests are good.

RubyConf 2007 First Day Morning

November 2nd, 2007

I’m at RubyConf 2007 for the next few days. Here’s a stream-of-consciousness blog of the first morning’s talks. Apparently there will eventually be video of the talks online.

RubyConf shirt

Photo by jremsikjr

David Black kicks it off

This year is bigger than ever, with attendance 15 times greater than the first one in 2001. New tracks have been added & the format has been changed with plenary sessions for the mornings and 3 tracks in the afternoon.

Marcel Molina: What Makes Code Beautiful?

Historical definitions of beauty

Beautiful things according to the audience:

  • My Wife
  • “His Wife”
  • Kids
  • Flowers
  • Expressiveness
  • Simplicity

Marcel Molina

Photo by dwortlehock

“Unlike most of the room, I wasn’t doing awesome BASIC hacks when I was 5 [years old]. I was reading books.” Marcel was interested in language and semantics, especially the differences between very similar sentences & constructions. There are good ways of constructing sentences and bad ways, especially for software.

If you make a really long sentence, with lots of relatively, if interesting, long clauses that don’t really do anything but keep the audience from knowing the important bits (because of huge delay and “suspension”), you suck.

Ruby appealed to Marcel almost immediately on some root level, although he wasn’t entirely aware of why. This is half of why “My Wife” is beautiful but you can’t explain why (“I just feel it” versus “her jawline is the golden ratio”). This makes peoples assertions that Ruby is elegant interesting to try to quantify.

Beauty for Software

Three parts of beauty (from Aquinas):

  • Proportion (you could make your hand 10 times bigger and it’d still be ok, but not if you didn’t preserve the ratio of sizes)
  • Integrity (a crystal hammer might be beautiful, but isn’t much of a hammer)
  • Clarity (“complicated in the perjorative sense”)

Now, a case study in code. The background is a web service that reads in a huge chunk of XML and builds Ruby objects as strings, but it’d be really nice to coerce those strings into appropriate objects:

'true'                     => true
'false'                    => false
'42'                       => 42
'2007-08-01T23:55:35.000Z' => Wed Aug 01 23:55:35 UTC 2007

The basic attempt is just to try a bunch of different coercions, one after the other, in a special try {} block.

So, how beautiful is this CoercibleString? It’s fairly proportionate, but that doesn’t really matter, we’re more interested in the appropriate size measure of proportionality. In this case, it’s not the appropriate size, because he later
refactored it to half the number of lines (from ~20 to 10). Does it have integrity, in that it is well suited to what it does? He uses the Generator library, which uses continuations in 1.8 (but threads in 1.9), and it was crazy slow and had a memory leak, so it doesn’t have integrity. Clarity? “Uh… yeah.” It had to be explained to everyone (unlike the refactored). Anyway, it failed
on all three.

Short Code: WTF

Photo by jnunemaker

Remember, all three parts of our definition are necessary (and no one can excluded). You lose clarity if you go too far on shortness:

expand(join("", (map { /\s+\w/ ? ( $_ ....

Does quality relate to beauty

Many engineers seem to not always care about beauty (and “feelings”), great software and beauty go hand & hand. For example, Kent Beck in Smalltalk: Best Practice Patterns is just an exploration of the best ways to design and write software. He might not use the word “beauty” or think about it in that way, but his ideas on rules to write great software is based on the same principles of beauty oulined above.

Is any of this useful?

This refactored coerce method may not be the most beautiful thing on its own, but compared to assembler, it’s stunning.

class String
  def self.coerce(string)
    case string
    when 'true':          true
    when 'false':         false
    when /^[1-9]+\d*$/:   integer(string)
    when DATETIME_FORMAT: Time.parse(string)
    else
      string
    end
  end
end

Ruby may not be the most beautiful thing in 20 years, but it certainly is today. If you’re not pleased with the beauty of the case statement above, consider the time before if was implemented in programming languages, then consider how beautiful if was when it was first added.

“Luckily for us, Ruby is optimized for beauty.” “When you’re working on software, try to imagine better modes of expression.” After giving it a try, make sure it doesn’t violate any of the three rules of beauty. Iterate until it passes all three and you’ll hopefully end up with something beautiful.

Hats off to Matz & and the Rubycore team for making such a beautiful language.

Jim Weinrich: Advanced Ruby Class Design

Jim’s history in programming and OO meant that while he’d used dynamic languages, his sense of OO was all from a strict paradigm. Coming from Java and C++ will give you a lot of good concepts, but there are parts of Ruby that are inconceivable in Java.

Jim Weirich

Photo by dwortlehock

Master of Disguise

This is an example from Rake (Rake::FileList).

RUBY_FILES = FileList['lib/**/*.rb']

FileList is like an Array, except that it initializes with a GLOB from the filesystem, has a specialized to_s method, uses lazy evaluation (woot), and has some extra methods (ext (for file extension manipulation), pathmap).

The first pass at this took the similarity to Array and started with that explicitly:

class FileList < Array
 ...
end 

Java would suggest that you never inherit from concrete classes, which also is a good rule for Ruby but for totally different reasons. More on that later.

The lazy bits made direct Array access problematic (like index), so each Array-accessing method had to call the resolve method to unlazyify the FileList. This made some operations not work.

Instead of inheriting from Array, you should use to_ary, so that Ruby helps you when messing with other Arrays out in the world. FileList now is just a regular class not inheriting from anything special and Ruby will ask a FileList if it can behave as an Array (using to_ary).

As for all of the methods needing to call resolve, you can DRY this with a list of relevant methods and a class_eval.

Takeaway: Consider to_ary/to_str when you want to mimic a base class, rather than using inheritance.

Doing nothing

Jim built Builder for the pure fun of it. (Thanks, Jim, it’s a pretty nice library!) It uses block structure and method_missing to make writing XML much easier. However, because XML element names may conflict with builtin methods (class is a good example), we have to make sure xml.class("Intro to Ruby") doesn’t blow up.

Wouldn’t it be nice to inherit from Object without inheriting all the stuff from Object? Introducing BlankSlate, which is really easy to write:

class BlankSlate
  instance_methods.each do |name|
    undef_methods name
  end
end

…but that’s a little too overzealous, because it removes /^__/ methods (__id__ is used by a lot of internal stuff, for example). That can be easily fixed with an unless.

Unfortunately, you’ve still got problems with global methods defined later (in Kernel, say). You can fix this by adding to the method_added hook in Kernel and Object:

alias_method :original_method_Added, :method_added
def method_added(name)
  result = original_method_added(name)
  BlankSlate.hide(name) if self == Kernel
  result
end

All set? Not quite, we’ve still got a similar bug that bypasses method_added:

module Name
  def name
    "My Name"o
  end
end 

class Object
  include Name
end  

...
xml.name("jim")

This can be fixed with the append_features hook (look at BlankSlate in Builder).

Parsing without Parsing

Consider:

User.find(:all, :conditions =< ["name = ?", "jim"])

…which looks a lot like SQL code. Why can’t I just call select?

user_list.select {|user|
  user.name == "jim"
}

“Wouldn’t it be nice if there was a way we could use select on ActiveRecord models?” Let’s write it (naive first attempt):

class User
  def self.select(&block)
    find(:all).select(&block)
  end
end  

This, of course, is not effecient at all, and large tables will kill you. Databases do really have a purpose, of course, and we should be using their design. Here’s a magical method:

def self.select(&block)
  cond = translate_block_to_sql(&block)
  find(:all, :conditions => cond)
end 

…however, not many people have written Ruby parsers, which is an “interesting language to parse”. You could use ParseTree, which uses ruby to parse Ruby then evicerates the result. Could we just execute the code? (huh?)

Here’s some curious code:

$ irb -rnode1
>> user = TableNode.new("users")
>> result = user.name
>> puts result.to_s
users.name
>> result2 = user.age
>> puts result2.to_s
users.age

This allows for references to tables that helps build SQL code. Here’s the background (similar for MethodNode):

class TableNode < Node
  def initialize(table_name)
    @table_name = table_name
  end
  def method_missing(sym, *args, &block)
    MethodNode.new(self, sym)
  end
  def to_s
    @table_name
  end
end

OK, we’ve got field references down, but how do we do stuff like ==?

class Node
  def ==(other)
    BinaryOpNode.new("=", self, other)
  end
end

…well we just capture the interesting method in the Node class then translate the method to a SQL fragment (in BinaryOpNode)

$ irb -rnode1res1 = (user.age == 50)
>> user = TableNode.new("users")
>> puts res1.to_s
(users.age = 50)
>> res2 = (user.name == "jim")
>> puts res2.to_s
(users.name = jim) # oops, no quotes

To quote strings, we need to differentiate between LiteralNodes and StringNodes, which just wraps with quotes (and probably does escaping). Getting the right kind of Node could depend on a case statement, but that’s not very OO. Every object should really know how to convert itself…

class Object
  # be careful opening core classes, which is why we have a unique name
  def as_a_sql_node
    LiteralNode.new(self)the r
  end
end  

class String
  def as_a_sql_node
    StringNode.new(self)
  end
end

Now we just need to call it:

  def ==(other)
    BinaryOpNode.new("=", self, other.as_a_sql_node)

end

…and, as we’d hoped:

>> res2 = (user.name == "jim")
>> puts res2.to_s
(users.name = 'jim') 

Problems

We haven’t handled commutativity [hey, Marcel had this problem too!]. "jim" == user.name will not work, although + gets help from coerce for mathematical operators. A killer problem is that &&/|| aren’t methods (by necessity, because of their shortcircutyness). !/!= also have predefined semantics and aren’t overridable. So, this technique really wouldn’t work for SQL. It’s a “solution looking for a problem.”

What did we learn?

Programming languages shape the way you think, so make sure you’re thinking about problems in a Ruby-ish way. Sometimes, the corners of a language will hold the keys to good, idiomatic design. Don’t be afraid of unusual solutions (some of the time).

MacBook Pro into Camcorder Hack

October 16th, 2007

I scrambled this week to try and figure out a way to record Alex Jacobson speak about HAppS at the last BayFP meeting. At the last moment, it occurred to me that I could at least capture it using a rather baroque hack:

Requirements

  • MacBook Pro with iSight camera
  • Screencasting software (I use SnapZ Pro, $69) w/Audio recording
  • iMovie (or something that can do a flip)

Note: Apple has made this much easier if you have a recent version of iLife (I don’t):

iLife ’08 makes even more of the built-in iSight camera. It’s simply another camera source for iMovie ’08, but that opens up another world of creative possibilities. Record a short clip with no extra hardware. Just MacBook Pro.

The Hack

The basic hack is quite simple:

  1. Bring up Photo Booth
  2. Get the computer really close to the presenter
  3. Capture Photo Booth on-screen display using screencasting software & record using MacBook Pro mic
  4. Flip the movie in iMovie
  5. Profit!

The Details

Turn it to 11

In watching recorded presentations on Google Video & elsewhere, I’ve decided that sound quality is the most important part of any video that you’re hoping to learn from. As long as the presenter has released their slides and aren’t using the blackboard much, you need very little picture quality to sync your own copy of the slides (hopefully readable) to the video’s unreadable version. However, if you don’t have good (or at least loud) audio you’re up a creek. It’s amazing how many of the Google Tech Talks, for example, have reasonable audio quality, but no where near enough volume to be understandable.

To ensure that you’re getting the best possible sound using this hack, make sure you’re putting the laptop as close to the presenter as is practicable. After that, boost your mic’s volume under System Preferences→Sound→Input. You’re looking for your Internal Microphone’s Input Volume (maybe using “Use ambient noise reduction”, though it’d really be much smarter to make everyone shut up and hope the AC doesn’t kick on). After boosting this volume, just watch for a couple of seconds to make the Level meter isn’t spiking. Even in the best conditions, the laptop will probably be far enough from the speaker to not have this be an issue.

Lights

This one is obvious, but make sure you’ve struck a balance in ambient light levels between annoying the audience, illuminating the speaker, and keeping the projector visible.

The Flip

Once you’ve recorded your presentation, you’ll notice that the resulting video is “backwards” (flipped) horizontally. If you load the file into iMovie you can fix this using the Mirror effect (under Video FX) with the Horizontal slider all the way to Left, Vertical at Top, and the Effect In & Out settings unchanged.

San Francisco Bay Area FP Group

August 21st, 2007

I’d like to announce the formation of the Bay Area Functional
Programmers group
. This group is for anyone using or interested in
functional programming and functional programming languages,
particularly strongly typed languages such as Haskell, OCaml, SML,
etc.

The first meeting will be Thursday, September 13th at 7:30pm somewhere
in San Francisco. Please join the mailing list at
http://groups.google.com/group/bayfp and suggest a location. The
initial meeting will be a casual pizza and beer get together, although
going forward we’d like to also include speakers, reading and
discussion of technical papers, and some hands on coding. Future
announcements and the location of the first meeting will be posted to
the BayFP mailing list.

More information will be available on the website: http://bayfp.org/.

Keith (+ Mike Wells, of Skydeck, who deserves most of the credit for initiating this)

How to Present Well (like Joe Gregorio)

July 25th, 2007

A nice tidbit from Joe’s talk at Oscon 2007:

Exposition: I can lie if you can learn

Stacked Bar Charts in JRuby using JFreeChart

July 12th, 2007

I was getting some comments on a previous post asking about building stacked bar charts in JRuby using JFreeChart, so here’s another example:

# Mostly inspired by 
# http://left.subtree.org/2007/01/15/creating-sparklines-with-jfreechart/
# have JFreeChart in your classpath, obviously, as well as jcommon.jar
require 'java'

module Graph
  class StackedBar
    include_class 'java.io.File'
    include_class 'org.jfree.chart.ChartUtilities'
    include_class 'org.jfree.chart.JFreeChart'
    include_class 'org.jfree.data.category.DefaultCategoryDataset'
    include_class 'org.jfree.chart.ChartFactory'
    include_class 'org.jfree.chart.plot.PlotOrientation'

    def initialize(width=600, height=400, data=[])
      @width = width
      @height = height
      dataset = create_sample_data() if data.empty?
      @chart = create_chart(dataset)
    end

    def render_to_file(filename, format="png")
      puts "Rendering graph to #{filename}"
      javafile = java.io.File.new(filename)
      ChartUtilities.saveChartAsPNG(javafile, @chart, @width, @height)
    end

    private
    def create_sample_data
      dataset = DefaultCategoryDataset.new
      dataset.addValue(1, "Submitted", "A")
      dataset.addValue(1, "Assigned", "A")
      dataset.addValue(3, "In-work", "A")
      dataset.addValue(1, "InVerfication", "A")
      dataset.addValue(2, "Delivered", "A")
      dataset.addValue(2, "Submitted", "B")
      dataset.addValue(1, "Rejected", "B")
      dataset.addValue(1, "Closed", "B")
      dataset.addValue(1, "Submitted", "C")
      dataset.addValue(1, "Assigned", "C")
      dataset.addValue(3, "In-work", "C")
      dataset.addValue(2, "On-hold", "C")

      return dataset
    end

    def create_chart(dataset)
      chart = ChartFactory.createStackedBarChart("XYZ's Development Projects",
                                                 "Project Name", 
                                                 "Hours", 
                                                 dataset,
                                                 PlotOrientation::VERTICAL, 
                                                 true, 
                                                 true, 
                                                 false)
      return chart    
    end
  end # class StackedBar  
end # class Graph

sb = Graph::StackedBar.new
sb.render_to_file("stacked_bar.png")

Here’s the output:

Stacked Bar chart example

Code here: http://kfahlgren.com/code/stacked_bar.jrb

More Clever GMail Ads for Programmers

July 10th, 2007

Just like the folks from Jane Street Capital, Swivel really knows how to write good, eye-catching ad copy (for some crazy subset of the population):

Clever Swivel Rails Ad

Scanning Slides

June 24th, 2007



Scanning Slides

Originally uploaded by abdelazer

I spent most of the weekend (a couple of weekends ago now) scanning my old slides on a rented Nikon 35mm scanner (rented happily from Pro Camera Rental). Working on my photographs (mainly from my digital P+S) in Lightroom during the recent months has reawakened my interest in photography, so I’m hoping that I can find some good images to work on from older slides I’ve taken.

The scanner itself, a Nikon Coolscan IV, produced very high quality TIFFs (64MB/slide) but was quite slow. My final average scanning rate was about 20 slides an hour.

DocBook-XSL Sytlesheets have >600 Parameters

June 13th, 2007

Norm Walsh writes:

Stylesheets can have literally hundreds of parameters. The DocBook XSL Stylesheets have more than six hundred.

All I can say at this point is: wow. Grepping the core of our own customization shows 121 <xsl:param>s (about 20 of which we introduced) and 52 <xsl:attribute-set>s (20, again). Thinking about it now (as I haven’t before), we’ve probably minimized that number by completely overriding 13 of the “regular” fo/ stylesheets directly (rather than using params or smaller, single-template overrides). The DocBook-XSL sytlesheets are a truly impressive, complex project.

Their complexity brings me to the other DocBook-related news item from today, in which Bob DuCharme argues that XHTML 2:

will hit a sweet spot between the richness of DocBook and the simplicity of XHTML 1

I’m certainly hopeful that our work in the DocBook SubCommittee for Publishers will move a subset of DocBook closer to that “sweet spot”.

Partial Updates: A Simpler Strawman?

June 10th, 2007

James Snell has been working some interesting things as the work on the Atom Publishing Protocol spec winds down. Most recently, he posted some thoughts on how to effectively communicate partial updates to APP servers using HTTP PATCH.

[UPDATE: James points out the obvious drawback to this approach in his response.]

One of the things that surprised me when I met other APP implementors at the interop was the relative lack of concern they seemed to have about the actual content inside their <atom:entry>s. This may have simply been a simplification on their part for the sake of testing (“if it can accept a single line of XHTML div it can accept anything, essentially) rather than their real views, but to someone very concerned about perfect content fidelity, it sorta scared me. These tiny <atom:entry>s might hide the some of the problems that APP will face in the wild, particularly for document repositories.

Long before the interop, we’d decided internally at O’Reilly to use the Media Resources rather than the <atom:entry> container (in large part because of the size of our DocBook documents, often over 2MB) for our document repository implementation. Because of the larger size of our content blocks, the sort of partial updates that James is thinking about might be quite cool.

The core of James’ strawman is an XML delta syntax (with credit due to Andy Roberts‘ work on the same) for HTTP PATCH with 8 operations: insert-before, insert-after, insert-child, replace, remove, remove-all, set-attribute and remove-attribute. Coming at this problem with my experience in document transformation and XSLT, I saw 7 of those operations (everything but ‘replace’) as unnecessary. The basic inspiration is thinking about each operation as an XSLT template. Mentally translate the d:replace/@path into xsl:template/@match and swap the bodies and you’ll be with me (with luck!).

Here’s the specific rundown of the 7 operations other than ‘replace’ working with James’ simple example <atom:entry>:

 1 <?xml version="1.0"?>
 2 <entry xmlns="http://www.w3.org/2005/Atom">
 3   <id>http://example.org/foo/boo</id>
 4   <title>Test</title>
 5   <updated>2007-12-12T12:12:12Z</updated>
 6   <summary>Test summary</summary>
 7   <author>
 8     <name>James</name>
 9   </author>
10   <link href="http://example.org"/>
11 </entry>

Note: You’ll have to imagine these working on a much larger XML document than my examples to understand the importance.

insert-before

 1 PATCH /collection/entry/1 HTTP/1.1
 2 Host: example.org
 3 Content-Type: application/delta+xml
 4 Content-Length: nnnn
 5 
 6 <d:delta
 7   xmlns:d="http://purl.org/atompub/delta"
 8   xmlns="http://www.w3.org/2005/Atom"
 9   xmlns:atom="http://www.w3.org/2005/Atom"
10   xmlns:b="http://example.org/foo">
11 
12   <!-- substitute for insert-before
13        /atom:entry/atom:author/atom:name
14        an atom:email -->
15   <d:replace path="/atom:entry/atom:author">
16     <atom:author>
17       <atom:email>james@example.org</atom:email>
18       <atom:name>James</atom:name>
19     </atom:author>
20   </d:replace>
21 </d:delta>

insert-after

 1 PATCH /collection/entry/1 HTTP/1.1
 2 Host: example.org
 3 Content-Type: application/delta+xml
 4 Content-Length: nnnn
 5 
 6 <d:delta
 7   xmlns:d="http://purl.org/atompub/delta"
 8   xmlns="http://www.w3.org/2005/Atom"
 9   xmlns:atom="http://www.w3.org/2005/Atom"
10   xmlns:b="http://example.org/foo">
11 
12   <!-- substitute for insert-after
13        /atom:entry/atom:author/atom:name
14        an atom:uri -->
15   <d:replace path="/atom:entry/atom:author">
16     <atom:author>
17       <atom:name>James</atom:name>
18       <atom:uri>http://example.org/blogs/james</atom:uri>
19     </atom:author>
20   </d:replace>
21 </d:delta>

insert-child

 1 PATCH /collection/entry/1 HTTP/1.1
 2 Host: example.org
 3 Content-Type: application/delta+xml
 4 Content-Length: nnnn
 5 
 6 <d:delta
 7   xmlns:d="http://purl.org/atompub/delta"
 8   xmlns="http://www.w3.org/2005/Atom"
 9   xmlns:atom="http://www.w3.org/2005/Atom"
10   xmlns:b="http://example.org/foo">
11 
12   <!-- substitute for insert-child
13        /atom:entry/atom:author
14        an atom:uri -->
15   <d:replace path="/atom:entry/atom:author">
16     <atom:author>
17       <atom:name>James</atom:name>
18       <atom:uri>http://example.org/blogs/james</atom:uri>
19     </atom:author>
20   </d:replace>
21 </d:delta>

remove

 1 PATCH /collection/entry/1 HTTP/1.1
 2 Host: example.org
 3 Content-Type: application/delta+xml
 4 Content-Length: nnnn
 5 
 6 <d:delta
 7   xmlns:d="http://purl.org/atompub/delta"
 8   xmlns="http://www.w3.org/2005/Atom"
 9   xmlns:atom="http://www.w3.org/2005/Atom"
10   xmlns:b="http://example.org/foo">
11 
12   <!-- substitute for remove
13        /atom:entry/atom:author/atom:name -->
14   <d:replace path="/atom:entry/atom:author/atom:name">
15   </d:replace>
16   <!-- yeah, this no atom:author is longer valid ..-->
17 </d:delta>

remove-all

 1 PATCH /collection/entry/1 HTTP/1.1
 2 Host: example.org
 3 Content-Type: application/delta+xml
 4 Content-Length: nnnn
 5 
 6 <d:delta
 7   xmlns:d="http://purl.org/atompub/delta"
 8   xmlns="http://www.w3.org/2005/Atom"
 9   xmlns:atom="http://www.w3.org/2005/Atom"
10   xmlns:b="http://example.org/foo">
11 
12   <!-- substitute for remove
13        /atom:entry/atom:author/atom:name -->
14   <d:replace path="/atom:entry/*">
15   </d:replace>
16   <!-- yeah, this atom:entry is no longer valid ..-->
17 </d:delta>

set-attribute

 1 PATCH /collection/entry/1 HTTP/1.1
 2 Host: example.org
 3 Content-Type: application/delta+xml
 4 Content-Length: nnnn
 5 
 6 <d:delta
 7   xmlns:d="http://purl.org/atompub/delta"
 8   xmlns="http://www.w3.org/2005/Atom"
 9   xmlns:atom="http://www.w3.org/2005/Atom"
10   xmlns:b="http://example.org/foo">
11 
12   <!-- substitute for set-attribute
13        /atom:entry/atom:link/@href 
14        to http://not-example.org -->
15   <d:replace path="/atom:entry/atom:link/@href">http://not-example.org</d:replace>
16 </d:delta>

remove-attribute

 1 PATCH /collection/entry/1 HTTP/1.1
 2 Host: example.org
 3 Content-Type: application/delta+xml
 4 Content-Length: nnnn
 5 
 6 <d:delta
 7   xmlns:d="http://purl.org/atompub/delta"
 8   xmlns="http://www.w3.org/2005/Atom"
 9   xmlns:atom="http://www.w3.org/2005/Atom"
10   xmlns:b="http://example.org/foo">
11 
12   <!-- substitute for remove-attribute
13        /atom:entry/atom:link/@href -->
14   <d:replace path="/atom:entry/atom:link">
15     <atom:link/>
16   </d:replace>
17   <!-- you can't take the easy way and match
18        the attribute, because an empty attribute
19        (@attr="") means something different than
20        the absence of @attr -->
21   <!-- and this atom:link is longer valid ..-->
22 </d:delta>

I think the above could be fairly easily implemented as a transformation into either XQuery or XSLT, but I’d imagine that it could be implemented using streaming techniques as well. Thoughts?