This document discusses playfulness in the workplace and how it relates to the Ruby programming language. It begins by thanking the organizers and hosts for the event. It then highlights some Ruby libraries created by the host city's Ruby community. The document goes on to discuss reasons for using Ruby at work, including dealing with data formats, scripting other software, sharing code with coworkers, and deploying software to customers. It provides examples using Ruby libraries and tools like Parslet, FFI, ChunkyPNG, and WinGui to parse data formats, control the mouse through its API, read PNG files, and click on points on the screen. The overall message is that Ruby can be used playfully to get tasks done at
2. playfulness at work
a real serious message™ with Ruby as the medium
Ian Dees / @undees
Hi, I’m Ian. I’m here to talk about playfulness at work, and how that relates to Ruby.
3. 1. playfulness in the workplace...
2. ...or the way playfulness works?
“Playfulness at Work”—does that mean “bringing play into the workplace” or “the way playfulness
works?”
9. their stuff will help your stuff
flog
flay
heckle
... and much more!
I’ll just highlight a couple of seattle.rb libraries here.... flog and flay report trouble spots in your
code: methods that are too long or too similar; that kind of thing. heckle inserts bugs into your
code and makes sure each bug results in a failing test case. If you’ve never run these before, catch
me in the hallway later and I’ll embarrass myself by running them on my own code for you.
10. Okay, on to the topic at hand. I make oscilloscopes. There’s something gratifying about making
something physical that lets you see into an invisible world.... But what does that have to do with
Ruby?
11. why this talk?
In other words, why this talk? Well, it turns out that even in a big organization like the one where I
work, there are scattered pockets of Ruby resistance against the C++ empire.
12. why Ruby?
Why is that? Aren’t we supposed to be writing embedded software here? Close. As Rich Kilmer
reminded us yesterday, what we’re supposed to do is create something that solves a problem for
someone. That might mean embedded code, a web front end for some engineering data, a one-off
shell script, a document, or nothing at all.
14. Anyway.... On those days when we need to munge some data or glue a couple of programs
together, Ruby comes to the rescue.
15. why bother?
There’s always a temptation is to follow the path of least acceleration and just do what the
organization thinks it wants to do. Fighting that temptation keeps our creative impulses sharp—
paradoxically resulting in our doing a better job than if we’d just done what was asked or expected
of us.
16. why this talk?
why bother?
why Ruby?
why?
why?
why?
So, all these questions seem like they’re leading somewhere.
18. It was about sneaking Ruby into work through the back door, using it to get little scripting tasks
done.
19. what’s changed?
we can walk in through the front door now!
Stealth was the only option back then. We had to prove ourselves with any unknown technology.
And now... Ruby is in the top twenty languages, according to that chart we saw yesterday. We’ve got
a little more leeway to storm the workplace and use Ruby when it suits the task at hand.
20. 1. dealing with crusty data
formats and protocols
...in a lighthearted way
I’d like to talk about a few of these kinds of those situations now, mainly as an excuse to bring up
some Ruby libraries you may find useful.
25. 1. dealing with crusty data
formats and protocols
...in a lighthearted way
First, data parsing. I work at a bigco, where we’ve seen ad hoc data formats come and go. For
example, you might hear, “That file full of test logs we just sort of started cutting and pasting into…
can you figure out how many times the Foo subsystem called into the Bar module?” The goal: get
the answer quickly with your sanity intact.
26. introducing my own
ad hoc format...
Just to get a taste of how easy it is to wrangle arbitrary data in Ruby, I’ll introduce a toy format and
then show a few lines of code to parse it.
27. TaskParchment
(with apologies to TaskPaper)
- Invent my own crufty data format
- Start with a good format @done
- Ruin it @badidea @done
- ???
- Profit!
The format will be based on TaskPaper, which seems to be a reasonable format for to-do lists.
Except I’m going to take everything cool out of it, so we can meaningfully talk about it in a few
slides. So, it’s like TaskPaper, but thinner: ergo, TaskParchment.
28. kschiess’s Parslet DSL
https://github.com/kschiess/parslet
We’ll use the Parslet library for picking apart the data. It’s based on the idea of Parsing Expression
Grammars—regular expressions attached to bits of code. Sounds like a hack, but there’s actually a
solid theoretical foundation for it.
29. describe TaskParchment::Parser do
before { @parser = TaskParchment::Parser.new }
it 'parses a task' do
input = "- Task @taggedn"
expected = {:desc=>"Task ", :tags=>[{:tag => "tagged"}]}
@parser.task_line.parse(input).must_equal expected
end
# ...
end
The first stage of parsing is to get the text into some kind of in-memory representation of a tree.
Parslet’s representation is a conglomeration of hashes and arrays. Here’s a first taste. Notice we’re
using minitest (built into Ruby 1.9.2) to do our testing. We can invoke a single parsing rule at a time
—in this case the yet-to-be-written “task_line” rule. This is a big help for testing.
30. require 'parslet'
module TaskParchment
class Parser < Parslet::Parser
# Example:
#
# - This is a task @tag
#
rule(:task_line) {
begin_task >>
description.as(:desc) >>
tags.maybe >>
newline
}
# ...
end
end
Here’s the implementation of that rule. See how fluidly Parslet reads: a single line of a task consists
of punctuation, followed by a description, possibly some tags, and finally a newline. Each of these
components is itself a rule. Let’s define those next.
31. rule(:begin_task) { str('- ') }
rule(:description) { desc_char.repeat(1) }
rule(:desc_char) { match('[^@n]') }
rule(:newline) { str("n") }
These are the the basic building blocks of a task. Notice that we can match exact strings or regular
expressions.
32. rule(:tags) { tag.repeat(1).as(:tags) }
rule(:tag) {
begin_tag >>
tag_char.repeat(1).as(:tag) >>
space.maybe
}
Here’s how we define tags for a task. Notice the “as(:tag)” marker. This is a signal to Parslet to
discard all the other stuff (spaces and punctuation) and just keep the tag name in the final in-
memory tree.
33. rule(:begin_tag) { str('@') }
rule(:tag_char) { match('[^@ n]') }
rule(:space) { str(' ') }
Here are the last few pieces of punctuation we haven’t defined yet.
34. $ ruby test_task_parchment.rb
Loaded suite test_task_parchment
Started
.
Finished in 0.001903 seconds.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 23512
Now our tests pass.
35. whew!
Let’s take a breath for a sec, before jumping into tasks that contain subtasks.
36. it 'parses a terrible plan into a tree' do
input = <<HERE
- Invent my own crufty data format
- Start with a good format @done
- Ruin it @badidea @done
- ???
- Profit!
HERE
# ...
end
Here’s the original TaskParchment string we wanted to parse.
37. it 'parses a terrible plan into a tree' do
# ...
expected =
[{:desc=>"Invent my own crufty data format",
:subtasks=>
[{:desc=>"Start with a good format ",
:tags=>[{:tag=>"done"}],
:subtasks=>[]},
{:desc=>"Ruin it ",
:tags=>[{:tag=>"badidea"}, {:tag=>"done"}],
:subtasks=>[]}]},
{:desc=>"???", :subtasks=>[]},
{:desc=>"Profit!", :subtasks=>[]}]
# ...
end
Here’s the in-memory representation we want.
38. it 'parses a terrible plan into a tree' do
# ...
@parser.parse(input).must_equal expected
end
And here’s the test assertion.
39. def task_line_indented_at(indent)
space.repeat(indent, indent) >> task_line
end
def task_indented_at(indent)
parser = task_line_indented_at(indent)
parser >>= task_indented_at(indent + 2).
repeat(0).as(:subtasks) unless indent > 20
parser
end
rule(:list) { task_indented_at(0).repeat(0) }
root(:list)
Believe it or not, we can do this with just a couple more parser rules. We’ll define these as functions
that take an indentation parameter. We’ll cap the indentation at 20 to prevent an infinite regression
in our parser (quite easy to do in PEG parsers). That “root” bit at the bottom gives the starting rule
for parsing an entire document.
40. $ ruby test_task_parchment.rb
Loaded suite test_task_parchment
Started
..
Finished in 0.009532 seconds.
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 63924
Two passing tests.
41. yay!
From here, Parslet has other tools for transforming those raw arrays and hashes into your own Ruby
classes. But let’s prepare to jump to another topic now. The lesson here is that we could have rolled
our own parser and had less fun (not to mention poorer syntax error reporting). Instead, we used
Ruby as an excuse for experimenting with a peculiar parsing technique.
42. 2. scripting other
people's software
...whether they know it or not
Next up: scripting. One day, you may be asked to write a data converter for a nasty binary format
that’s undocumented, unsupported, and has no developer hooks in the only software that knows
how to read it. You might try to reverse-engineer their API, or you might get nasty and just drive
their software through the user interface.
43. something silly
Rather than shame the vendor of this particular piece of software, I’d rather just go off in a different
direction and do something silly with FFI in Ruby. We’re going to write a script that renders a black-
and-white picture in the least efficient way possible: by clicking it pixel-by-pixel with the mouse.
44. chunky_png
https://github.com/wvanbergen/chunky_png
First, we need to crack open the picture and find the dark pixels. To do this, we’ll use the excellent
chunky_png, a pure-Ruby PNG decoder.
45. require 'chunky_png'
image = ChunkyPNG::Image.from_file filename
shadows = []
(0...image.height).each do |y|
(0...image.width).each do |x|
gray = ChunkyPNG::Color.grayscale_teint
image[x, y]
shadows << [x, y] if gray < 128
end
end
This code will build an array of the x/y locations we’ll need to click with the mouse.
46. FFI
https://github.com/ffi/ffi
To grab hold of the platform-specific mouse API, we’re going to use FFI, the foreign function
interface library that started in the Rubinius project and is now available in at least three other Ruby
implementations.
47. require 'ffi'
module User32
extend FFI::Library
ffi_lib 'user32'
ffi_convention :stdcall
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
attach_function :SetCursorPos, [:long, :long], :int
attach_function :mouse_event, [:long] * 5, :void
end
To simulate a mouse click on this particular platform, you need to import two functions: one to
position the mouse, and one to click it. Notice how the FFI functions look vaguely like C
declarations.
48. include User32
def mouse_click(x, y)
SetCursorPos x, y
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
end
Here’s how to actually simulate the click.
49. shadows.sort_by { rand }.each do |x, y|
mouse_click 100 + x, 100 + y
sleep 0.05
end
And here’s how we’ll draw the picture. To save a bunch of window-management code in these
examples, we’ll just assume the drawing app is maximized. We’ll give it a margin of 100 pixels each
way to make room for the toolbar.
50. win_gui
https://github.com/arvicco/win_gui
The code is even simpler if you use win_gui, a gem that provides FFI wrappers for common Windows
GUI calls.
51. require 'win_gui'
w = WinGui::Window.find :title => /- Paint$/
shadows.sort_by { rand }.each do |x, y|
w.click :point => [100 + x, 100 + y]
sleep 0.05
end
Here’s the entire drawing routine in win_gui. We don’t need any of the FFI setup stuff any more, or
the definition of a mouse click.
52. In the real-life story that inspired this example, we were able to convert the screen scraper to C
with basically a few macros in Emacs. The program did its job, and we abandoned it as soon as we
could to move on to more worthy projects.
53. 3. sharing your code
with co-workers
...without annoying them too much
Now, I’d like to talk about deployment. Once you’ve written that brilliant text parser, data converter,
or web front end, how do you share it with your co-workers who may be on an IT-supplied
Windows XP box?
54. running from source
The simplest way to share code internally is to just hand your co-worker a .rb file and be done with
it.
55. 2005
“just download Ruby from here”
In 2005, that worked pretty well. You could just tell people, “Download the all-in-one Ruby installer
from here, save this .rb file onto your machine, and run it.”
56. 2008
MSWin MinGW
1.8 easy scary
1.9 hard scary
Then life got complicated. Not only were there there two versions of Ruby, there were two
alternative Windows builds of Ruby. If your program depended on a gem that had C code in it, the
compiled code would only work in one particular Ruby variant.
57. 2010
MSWin MinGW
1.8 gone easy
1.9 gone easy
The story is much better now. MinGW has emerged as the winner of the C compiler battle, as far as
Ruby is concerned. And the Windows folks have found a way to deal with C code that works in both
1.8 and 1.9.
58. RubyInstaller
https://github.com/oneclick/rubyinstaller
It’s all thanks to Luis Lavena’s RubyInstaller, which installs Ruby onto your system and has an
optional dev kit that pretty much makes “gem install” work the same on Windows as it does on
UNIX. You still have to worry about dependencies, but this is a huge step forward.
59. self-contained
executables
Another approach is to build binaries for people that include enough Ruby for them to run your
program without an extra installation step.
60. OCRA
https://github.com/larsch/ocra
Windows Rubyists have OCRA, the One-Click Ruby Application builder. This will bundle up your
Ruby program with a copy of the interpreter and any gems you need into a single .exe file.
61. what shall we deploy?
Let’s pick an app to serve as a guinea pig, so we can show a few of these techniques.
62. holman’s Boom app
http://github.com/holman/boom
In keeping with the Hubot chat robot from yesterday, let’s show something that appears to have
made Zach Holman the fastest draw in Campfire: a command-line clipboard manager called Boom.
(Pronounce it “bewwwwwm.”)
64. require 'boom'
Boom::Command.execute(*ARGV)
C:boom> ocra boom.rb
boom.exe
This app has a gem dependency (on json-pure). As long as that’s installed on our system, OCRA will
find it. We just write a trivial .rb file to launch Boom, point OCRA at it, and we get a working exe.
65. Warbler
https://github.com/nicksieger/warbler
I’m partial to JRuby myself, because it makes it easy to use gems that have a compiled component.
JRuby binary gems (such as ActiveRecord database adapters) don’t have to be compiled for each
specific platform. Warbler is a JRuby build tool that looks at your app’s Gemfile and creates a
runnable .jar file that anyone with Java installed can run.
66. # bin/boom
require 'boom'
Boom::Command.execute(*ARGV)
# Gemfile
source :rubygems
gem 'boom'
gem 'httparty'
$ bundle
$ warble jar
boom.jar
The workflow with Warbler is nearly the same as with OCRA. Warbler keys off your project’s Gemfile,
rather than watching your require calls.
67. 4. deploying your
software
...to honest-to-goodness paying customers!
The techniques we’ve seen so far have been good enough for sharing code with co-workers, and
perhaps a few customers (at work, we’ve put OCRA-based binaries on our website for end users). At
some point, though, you begin to wonder, “Does this app need an installer?”
68. RailsInstaller
https://github.com/railsinstaller
If you wanted to build a full installer for a Ruby app, you could start with the RubyInstaller project
and then add some installation steps that contain your own Ruby code and dependencies. That’s
pretty much what the RailsInstaller project from Engine Yard does. It installs Ruby, Rails, Git, and
some other dependencies onto a Windows machine.
69. BoomInstaller
what would it take?
As a thought experiment, what would it take to turn the RailsInstaller into a BoomInstaller?
70. # railsinstaller.yml
---
:gems:
:name: Gems
:title: Ruby Gems
:category: gems
:list:
- boom
:boom:
:name: Boom
:title: Text snippets
:category: gem
It’s pretty easy, as it turns out. Dr. Nic and crew have made their installation recipes configurable.
You’d just need to add the Boom gem to a YAML config file.
71. # actions.rb
module RailsInstaller
def self.build!
components = [
BSDTar, SevenZip, DevKit, Ruby187,
]
components.each do |package|
section package.title
download package
extract package
end
link_devkit_with_ruby
end
end
Then you’d need to shorten the list of installed components (unless you want to deploy Git and SQL
bindings along with Boom).
72. ;railsinstaller.iss
-#define InstallerName "RailsInstaller"
+#define InstallerName "BoomInstaller"
-DefaultDirName={sd}RailsInstaller
+DefaultDirName={sd}BoomInstaller
-VersionInfoDescription=Rails development environment installer...
+VersionInfoDescription=Boom installer for Windows
;...
The ugliest part is editing the Inno Setup script. It’s mostly just grepping through for each
occurrence of Rails and deciding whether or not to replace it with Boom.
75. BoomInstaller
https://github.com/undees/railsinstaller-windows/
tree/boom
You can see the changes I made in the “boom” branch of my fork of RailsInstaller.
76. That wraps up the concrete portion of the program. Let’s zoom back out to the big questions of
why we’re doing this.
Image courtesy http://www.flickr.com/photos/chrisbevan/2476405
77. playfulness
For me, and I suspect for many of you, Ruby imparts a sense of playfulness. And in the next few
minutes, I’m going to prove that one can carefully cherry-pick a few items from Brain Science™ to
support the importance of play at work.
78. Brain Science™ on play
Stuart Brown Tim Brown
First, here’s a pair of TED talks on creative play. TED is the conference speaker’s ideal substitute for
research, by the way. Stuart Brown says, “Nothing lights up the brain like play,” and then proceeds
to tell us how that works, biologically speaking. Tim Brown explains how playfulness improves the
performance of entire teams.
79. Next, there’s material I was actually exposed to as a kid... on vinyl. It’s about how to harness the
huge parallel processing unit in our heads to solve problems in ways we didn’t expect. (See also
Andy Hunt’s Pragmatic Thinking and Learning). The gist is that play unlocks your brain’s problem-
solving engine.
80. Finally, there’s I Hate People, an airplane reading book that sounds like it’s going to be Dilbert
come to life, but does a Randy-Pausch-style head fake into shaping your career. The authors talk
about the notion of being a Soloist, someone who does creative work with a supportive ensemble at
work. tl;dr: by their scrappy entrepreneurship, Soloists drag their organizations kicking and
screaming to success.
81. Which brings us here. Over the past two days, you’ve heard how crucial it is to keep yourself
engaged and challenged in your work. You’ve heard about the importance of community in
promoting that virtuous cycle that feeds success into success. My hope is that Cascadia Ruby Conf
doesn’t end here. (We haven’t had lunch yet!) My hope is that we continue to connect, to support
one another, to stay playful, and to keep making great things. Long live Cascadia Ruby Conf.
Cheers.
82. slides and code
https://github.com/undees/cascadiaruby
Slides and code will be available at this address (eventually).