Scott Hanselman

The Weekly Source Code 29 - Ruby and Shoes and the First Ruby Virus

June 24, '08 Comments [11] Posted in Ruby | Source Code
Sponsored By

I was in Norway last week at the Norwegian Developers Conference, so I'm afraid the Weekly Source Code is the bi-weekly source code this month. It's amazing how international travel can really slow a body down. This trip just obliterated me. I'm slowly coming back to the world of the living, though.

I've been getting more and more interested in how folks extend their applications using plugins and things. In my new ongoing quest to read source code to be a better developer, Dear Reader, I present to you twenty-ninth in a infinite number of posts of "The Weekly Source Code."

This week's source is a clever "ClickOnce"-style hack for Ruby. It's cool because it brings together a number of technologies into a very clean end-user experience. The intent is to make the running of a Ruby GUI Application effortless, and it works and it's brilliant on several levels. Thanks to Sam Saffron for pointing me to it!

To start out, it's a hack says the author why the lucky stiff (or "_why" for short), so it's very early and there's bugs. You can check out the code on GitHub: http://github.com/why/shoes/tree/master or clone the whole tree using git via "git clone git://github.com/why/shoes.git"

Shoes

Ruby is a very aesthetically (to me) pleasing and flexible language. Shoes is a GUI Toolkit for making Windowing Applications using Ruby. I mentioned it back in TWSC12. Shoes is legendary for a number of reasons, but above all, it has the greatest API documentation in the history of all software documentation. The main book on Shoes Development called "nobody knows shoes" is fantastic. Even Chris Sells likes it!

Here's part of an example phone book application written in Ruby and Shoes):

Shoes.app :width => 320, :height => 350 do

stack :margin => 40 do
stack :margin => 10 do
para "Name"
@name = list_box :items => ["Yes, please!", "NO. No thankyou."]
end
stack :margin => 10 do
para "Address"
@address = edit_line
end
stack :margin => 10 do
para "Phone"
@phone = edit_line
end
stack :margin => 10 do
button "Save" do
Shoes.p [@name.text, @address.text, @phone.text]
end
end
end
end

It feels a lot like Tcl/Tk or Rebol. It'll be interesting to see if someone writes a mainstream application that is non-trivial. Shoes works on XP, Vista, MacOSX and Linux. Do note that you'll need to right click and run it as Administrator on Vista as _why hasn't put in an elevation prompt yet. You'll need Admin to do the initial bootstrap install.

However, in order to get Shoes to work, before today, you had to install Ruby, then get the Shoes libraries installed, usually via a gem. Then you'd need to run your app via ruby.exe yourapp.rb or a shortcut you'd setup.  The second comment on _why's blog post announcement says it all:

"Wow, this is super. We can actually give our apps to other people now :)"

Here's an explanation from _why on how the Shoes "bootstrapper" (my word) works:

If you poke around with a hex editor inside Windows’ PE binary format, you’ll find an .rsrc section at the end of the file which contains the icons and dialog boxes. I insert the Ruby script into this mess.
binj = Binject::EXE.new("blank.exe")
binj.inject("SHOES_FILENAME", "simple-accordion.rb")
File.open("simple-accordion.rb") do |f|
  binj.inject("SHOES_PAYLOAD", f)
end
binj.save("accordion.exe")

Fantastic hack. He shoves the Ruby script into a Windows Resource! He does the same thing for DMG (Disk Images) on Mac OSX, so you can create Ruby scripts that are portable to these two platforms.

When it runs, it checks for the existence of Shoes on your system. It's in C:\Program Files (x86)\Common Files\Shoes\. If Shoes (which includes a private copy of Ruby) is not there, it's downloaded and installed, and then your app runs. Bam, one-click cross-platform GUIs.

I'm not clear what the security ramifications are. Well, I am. I suspect there is no security that isn't already provided by the host operating system. Once you're running, the Ruby script has full control to do whatever it likes, so I suppose someone could write a malicious program just as they could write a malicious .NET app if they liked.

You can package your apps just by running shoes --package and you'll get this dialog. You can chose to include Shoes and Ruby inside the resulting EXE if you like.

 Shoes

Shoes also supports custom "Widgets." For example, here's a custom Speedometer control.

Shoes (2)

The code for the entire app, including the custom control is this:

class Speedometer < Widget
attr_accessor :range, :tick, :position
def initialize opts = {}
@range = opts[:range] || 200
@tick = opts[:tick] || 10
@position = opts[:position] || 0
@cx, @cy = self.left + 110, self.top + 100

nostroke
rect :top => self.top, :left => self.left,
:width => 220, :height => 200
nofill
stroke white
oval :left => @cx - 50, :top => @cy - 50, :radius => 100
(ticks + 1).times do |i|
radial_line 225 + ((270.0 / ticks) * i), 70..80
radial_line 225 + ((270.0 / ticks) * i), 45..49
end
strokewidth 2
oval :left => @cx - 70, :top => @cy - 70, :radius => 140
stroke lightgreen
oval :left => @cx - 5, :top => @cy - 5, :radius => 10
@needle = radial_line 225 + ((270.0 / @range) * @position), 0..90
end
def ticks; @range / @tick end
def radial_line deg, r
pos = ((deg / 360.0) * (2.0 * Math::PI)) - (Math::PI / 2.0)
line (Math.cos(pos) * r.begin) + @cx, (Math.sin(pos) * r.begin) + @cy,
(Math.cos(pos) * r.end) + @cx, (Math.sin(pos) * r.end) + @cy
end
def position= pos
@position = pos
@needle.remove
append do
@needle = radial_line 225 + ((270.0 / @range) * @position), 0..90
end
end
end

Shoes.app do
stack do
para "Enter a number between 0 and 100"
flow do
@p = edit_line
button "OK" do
@s.position = @p.text.to_i
end
end

@s = speedometer :range => 100, :ticks => 10
end
end

It's about 24 megs of libraries on disk when you're done, but you've got multimedia support and a lot of great library support in that space, not to mention a copy of Ruby itself. The download is only 2.5M bare or 6.8M with Video support.

I think it'd be great to build a Twitter Client using Shoes. The race is on! Hopefully we'll be able to get a Twitter Client done before some schmuck writes a Ruby Shoes virus. I for one, will be keeping the Ruby Shoes virus I wrote to myself. Well, not really a virus as it's a self propagating annoyance. But still. :)

It'll be fun to watch how this evolves. I hope there will be a clean upgrade process. If you want to try running your first Ruby/Shoes app, then run this EXE if you've got Windows or this DMG if you're on a Mac to see a basic demo application, but more importantly, to experience in the installer/bootstrapping process.

Technorati Tags: ,,

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Hanselminutes Podcast 118 - Lean Software Development with Tom and Mary Poppendieck

June 23, '08 Comments [6] Posted in Podcast
Sponsored By

image image My one-hundred-and-eighteenth podcast is up. In this episode, I sit down with Lean Software legends Tom and Mary Poppendieck and chat about their thoughts on the state of Software Development. Is Agile a fad? We'll also learn how my (and likely your!) definition of "success" is wrong.

Subscribe: Subscribe to Hanselminutes Subscribe to my Podcast in iTunes

If you have trouble downloading, or your download is slow, do try the torrent with µtorrent or another BitTorrent Downloader.

Do also remember the complete archives are always up and they have PDF Transcripts, a little known feature that show up a few weeks after each show.

Telerik is our sponsor for this show.

Telerik's new stuff is pretty sweet, check out the ONLINE DEMO of their new ASP.NET AJAX suite. RadGrid handles sorting, filtering, and paging of hundreds of thousands of records in milliseconds, and the RadEditor loads up to 4 times faster and the navigation controls now support binding to web services on the client.

As I've said before this show comes to you with the audio expertise and stewardship of Carl Franklin. The name comes from Travis Illig, but the goal of the show is simple. Avoid wasting the listener's time. (and make the commute less boring)

Enjoy. Who knows what'll happen in the next show?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Hanselminutes Podcast 117 - Sorting out Internationalization with Michael Kaplan

June 18, '08 Comments [10] Posted in Internationalization | Podcast
Sponsored By

Unicode_sample My one-hundred-and-seventeenth podcast is up. Michael Kaplan is a Developer in the Windows International group and the author of the popular 'Sorting It Out' blog that is dedicated it all things '-ization.' That means Globalization, Internationalization, and Localization. Do check out Michael's blog as well as the Internationalization category of this blog.

Subscribe: Subscribe to Hanselminutes Subscribe to my Podcast in iTunes

If you have trouble downloading, or your download is slow, do try the torrent with µtorrent or another BitTorrent Downloader.

Do also remember the complete archives are always up and they have PDF Transcripts, a little known feature that show up a few weeks after each show.

Telerik is our sponsor for this show.

Telerik's new stuff is pretty sweet, check out the ONLINE DEMO of their new ASP.NET AJAX suite. RadGrid handles sorting, filtering, and paging of hundreds of thousands of records in milliseconds, and the RadEditor loads up to 4 times faster and the navigation controls now support binding to web services on the client.

As I've said before this show comes to you with the audio expertise and stewardship of Carl Franklin. The name comes from Travis Illig, but the goal of the show is simple. Avoid wasting the listener's time. (and make the commute less boring)

Enjoy. Who knows what'll happen in the next show?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Hanselminutes Podcast 115 - Rediscovering Your Passion for Software

June 18, '08 Comments [7] Posted in Podcast | Programming
Sponsored By

carlfranklin (I forgot to post this one to my blog, so you may have already noticed it on the Hanselminutes Feed. My apologies.)

My one-hundred-and-fifteenth podcast is up. Carl Franklin returns in this episode, and he and I talk about the joys of programming and getting back to basics. Is it hard to stay passionate about this job? Is there a need for the community to revisit Computer Science 101? What do you do to stay excited about software?

Subscribe: Subscribe to Hanselminutes Subscribe to my Podcast in iTunes

If you have trouble downloading, or your download is slow, do try the torrent with µtorrent or another BitTorrent Downloader.

Do also remember the complete archives are always up and they have PDF Transcripts, a little known feature that show up a few weeks after each show.

Telerik is our sponsor for this show.

Telerik's new stuff is pretty sweet, check out the ONLINE DEMO of their new ASP.NET AJAX suite. RadGrid handles sorting, filtering, and paging of hundreds of thousands of records in milliseconds, and the RadEditor loads up to 4 times faster and the navigation controls now support binding to web services on the client.

As I've said before this show comes to you with the audio expertise and stewardship of Carl Franklin. The name comes from Travis Illig, but the goal of the show is simple. Avoid wasting the listener's time. (and make the commute less boring)

Enjoy. Who knows what'll happen in the next show?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Back to Basics - Life After If, For and Switch - Like, a Data Structures Reminder

June 13, '08 Comments [42] Posted in Back to Basics | Learning .NET | Programming | Source Code
Sponsored By

I just had a great one on one coding learning session with a good friend of mine over lunch. He's trying to take his coding skills to the "next level." Just as we plateau when we work out physically, I think we can plateau when coding and solving problems. That one of the reasons I try to read a lot of code to be a better developer and why I started The Weekly Source Code. He said it was OK to write about this as someone else might benefit from our discussion. The code and problem domain have been changed to protect the not-so-innocent.

One of the things that we talked about was that some programmers/coders/developers have just a few tools in their toolbox, namely, if, for, and switch.

I'm not making any judgements about junior devs vs. senior devs. I'm talking about idioms and "vocab." I think that using only if, for and switch is the Computer Programmer equivalent of using "like" in every sentence. Like, you know, like, he was all, like, whatever, and I was like, dude, and he was, like, ewh, and I was like meh, you know?

When speaking English, by no means do I have a William F Buckley, Jr.-sized vocabulary, nor do I believe in being sesquipedal for sesquipedalianism's sake, but there is a certain sparkly joyfulness in selecting the right word for the right situation. I'm consistently impressed when someone can take a wordy paragraph and condense it into a crisp sentence without losing any information.

Refactoring code often brings me the same shiny feeling. Here's a few basic things that my friend and I changed in his application over lunch, turning wordy paragraphs into crisp sentences. Certainly this isn't an exhaustive list of anything, but perhaps it can act as a reminder to myself and others to be mindful and think about solving problems beyond, like, if and for and, like, switch, y'know?

Massives Ifs are Sometimes Maps

He had some code that parsed a ridiculous XML document that came back from a Web Server. That the format of the XML was insane wasn't his fault, to be sure. We all have to parse crap sometimes. He had to check for the existence of a certain value and turn it into an Enum.

if (xmlNode.Attributes["someAttr"].Value.ToLower().IndexOf("fog") >= 0)
{
wt = MyEnum.Fog;
}
if (xmlNode.Attributes["someAttr"].Value.ToLower().IndexOf("haze") >= 0)
{
wt = MyEnum.Haze;
}
if (xmlNode.Attributes["someAttr"].Value.ToLower().IndexOf("dust") >= 0)
{
wt = MyEnum.Haze;
}
if (xmlNode.Attributes["someAttr"].Value.ToLower().IndexOf("rain") >= 0)
{
wt = MyEnum.Rainy;
}

...and this went on for 40+ values. There's a few problems with this.

First, he's using IndexOf() and ToLower() to when he's trying to say "ignoring case, does this string contain this other string?" Using ToLower() for a string comparison is always a bad idea, and not just because of the Turkish i problem (details here, here, here and here). Be sure to check out the Recommendations for Strings in .NET.

We could make this simpler and more generic with a helper method that we'll use later. It expresses what we want to do pretty well. If we were using .NET 3.5 we could make this an extension method, but he's on 2.0.

private static bool ContainsIgnoreCase(string s, string searchingFor)
{
return s.IndexOf(searchingFor, StringComparison.OrdinalIgnoreCase) >= 0;
}

Second, he's indexing into the Attributes collection over and over again, and he's hasn't "else cased" the other ifs, so every indexing into Attributes and every other operation runs every time. He can do the indexing once, pull it out, then check his values.

string ws = xmlNode.Attributes["someAttr"].Value;
if (ContainsIgnoreCase(ws, "cloud"))
wt = MyEnum.Cloudy;
else if (ContainsIgnoreCase(ws, "fog"))
wt = MyEnum.Fog;
else if (ContainsIgnoreCase(ws, "haze"))
wt = MyEnum.Haze;
else if (ContainsIgnoreCase(ws, "dust"))
wt = MyEnum.Dust;
else if (ContainsIgnoreCase(ws, "rain"))
wt = MyEnum.Rainy;
else if (ContainsIgnoreCase(ws, "shower"))

...and again, as a reminder, this goes on for dozens and dozens of lines.

We were talking this through step by step to explain my "from point a to point d" wait of thinking. I tend to skip b and c, so it's useful to show these incremental steps, kind of like showing all your work in Math class when doing long division.

At this point, I pointed out that he was clearly mapping the strings to the enums. Now, if the mapping was direct (and it's not for a variety of horrible many-to-one reasons that are spec-specific as well as that this a "contains" operations rather than a direct equality comparison) he could have parsed the string an enum like this where the last parameter ignores case:

wt = (MyEnum)Enum.Parse(typeof(MyEnum), ws, true);

However, his mapping has numerous exceptions and the XML is messy. Getting one step simpler, I suggested making a map. There's a lot of folks who use Hashtable all the time, as they have for years in .NET 1.1, but haven't realized how lovely Dictionary<TKey, TValue> is.

var stringToMyEnum = new Dictionary<string, MyEnum>()
{
{ "fog", MyEnum.Fog},
{ "haze", MyEnum.Fog},
{ "fred", MyEnum.Funky},
//and on and on
};

foreach (string key in stringToMyEnum.Keys)
{
if (ContainsIgnoreCase(ws, key))
{
wt = stringToMyEnum[key];
break;
}
}

Because his input data is gross, he spins through the Keys collection and calls ContainsIgnoreCase on each key until settling on the right Enum. I suggested he clean up his input data to avoid the for loop, turning the whole operation into a simple mapping. At this point, of course, the Dictionary can be shoved off into some global readonly static resource.

string ws = xmlNode.Attributes["someAttr"].Value;
//preproccessing over ws to tidy up
var stringToMyEnum = new Dictionary<string, MyEnum>()
{
{ "fog", MyEnum.Fog},
{ "haze", MyEnum.Fog},
{ "fred", MyEnum.Funky},
//and on and on
};
wt = stringToMyEnum[key];

When Switches Attack

Often large switches "just happen." What I mean is that one introduces a switch to deal with some uncomfortable and (apparently) unnatural mapping between two things and then it just gets out of hand. They tell themselves they'll come back and fix it soon, but by then it's grown into a hairball.

My buddy had a method that was supposed to remove an icon from his WinForms app. The intent is simple, but the implementation became another mapping between a fairly reasonable Enum that he couldn't control, and a number of named icons that he could.control.

The key here is that he could control the icons and he couldn't easily control the enum (someone else's code, etc). That the mapping was unnatural was an artifact of his design.

The next thing he knew he was embroiled in a switch statement without giving it much thought.

private void RemoveCurrentIcon()
{
switch (CurrentMyEnumIcon)
{
case MyEnum.Cloudy:
CustomIcon1 twc = (CustomIcon1)FindControl("iconCloudy");
if (twc != null)
{
twc.Visible = false;
this.Controls.Remove(twc);
}
break;
case MyEnum.Dust:
CustomIcon2 twd = (CustomIcon2)FindControl("iconHaze");
if (twd != null)
{
twd.Visible = false;
this.Controls.Remove(twd);
}
break;
//etc...for 30+ other case:

There's a few things wrong here, besides the switch is yucky. (Yes, I know there are uses for switches, just not here.) First, it's useful to remember when dealing with a lot of custom classes, in this case CustomIcon1 and CustomIcon2 that they likely have a common ancestor. In this case a common ancestor is Control.

Next, he can control the name of his controls. For all intents in his WinForms app, the Control's name is arbitrary. Because his controls map directly to his Enum, why not literally map them directly?

private void RemoveCurrentIcon(MyEnumIcon CurrentMyEnumIcon)
{
Control c = FindControl("icon" + CurrentMyEnumIcon);
if(c != null)
{
c.Visible = false;
Controls.Remove(c);
}
}

The recognition of the shared base class along with the natural Enum to Control name mapping turns 160 lines of switch into a single helper method.

It's very easy, seductive even, to try to explain to the computer to "do it like this" using if, for and switch. However, using even the basic declarative data structures to describe the shape of the data can help you avoid unnecessary uses of the classic procedural keywords if, for and switch.

FINAL NOTE: We worked for a while and turned and 8100 line long program into 3950 lines. I think we can squeeze another 1500-2000 lines out of it. We did the refactoring with the new Resharper 4.0 and a private daily build of CodeRush/Refactor!Pro. I'll do some writeups on both wonderful tools in the coming weeks.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.