Showing posts with label ruby. Show all posts
Showing posts with label ruby. Show all posts

Saturday, March 15, 2008

SVN hooks on Windows with Ruby

I'd been meaning to write a pre-commit hook to catch empty log messages for a while but hadn't got around to it for one reason or another. I had a spare 5 minutes so I got stuck in.

The SVN book mentions that on Windows the hook files extension basically has to be .exe, .com or .bat. Even though .rb is executable on my SVN server it was not being called. So, instead I just called the ruby script from the bat file as such:

pre-commit.bat
1 SET DIR=%1/hooks
2 set REPOS="%1"
3 set TXN="%2"
4
5 c:\ruby\bin\ruby.exe d:\svn\hooks\pre-commit.rb %REPOS% %TXN% %DIR%

It also mentions that for security reasons, the repository executes hook scripts with an empty environment—that is, no environment variables are set at all, not even $PATH or %PATH%. Because of this, it is necessary to specify absolute paths.

Setting DIR above is a bit of a trick for debugging purposes so you can write a log file into the hooks directory to check things out. The ruby script is as follows:

pre-commit.rb
 1 REPOS, TXN, DIR = ARGV[0], ARGV[1], ARGV[2]
 2 SVN_LOOK = "C:/Program Files/Subversion/bin/svnlook.exe"
 3
 4 log_msg = %x["#{SVN_LOOK}" log -t #{TXN} #{REPOS}].chomp.strip
 5
 6 if (log_msg.nil? || log_msg.size < 5)
 7   err = <<-EOE
 8     This commit has failed due to the absence of a meaningful log message.
 9     Please provide a message describing why you changed what you did and
10     then try committing again. Ta -- Dan
11   EOE
12
13   STDERR.puts err
14   exit(1)
15 end

Yeah, I'm a bit of a bastard setting the minimum length of the log message to 5 chars. The only tricky bit of the entire process was realizing that I needed to chomp a new line off the log message. That's where using DIR and writing to a log file helped out.

Tally, ho!

Friday, November 16, 2007

Get full name for Windows user from Ruby

Lately I've been writing a few automation programs and it was suggested by a colleague that I write one for logging a job with EDS, our "service" provider.

The web page is nasty to navigate, I think they make it hard so people decide to give up and go get a beer instead of logging a job :)

Anyway, one of the fields on the page requires the full name of the person logging the job. It's trivial to grab the username of the logged on user:

1 puts ENV['USERNAME']

However, I wasn't sure of the easiest way to grab the user's full name. There are a few packages out there, for example the sys-admin gem. It didn't work though, I dug around in the source and it hardcodes a cimv2 path, no good.

Seeing this is a quick hack running on windows for windows, rah rah rah, might as well boogey with it.

Enter ADSI.

The following interrogates our AD and grabs what I want. Too easy.

1 require 'win32ole'
2 puts WIN32OLE.connect("WinNT://your.ad.box.sa.gov.au/#{ENV['USERNAME']}").FullName

Thursday, October 4, 2007

Sequence Mapping Functions

I've slowly been making my way through Peter Seibel's Practical Common Lisp. Even though I'm also in between a few other books at the moment I've managed to let some of it soak in.

I came across the following example that shows off the quintessential map function.

1 (map 'vector #'* #(1 2 3 4 5) #(10 9 8 7 6))

It simply produces a new sequence by multiplying the subsequent elements of the supplied sequences, resulting in: => #(10 18 24 28 30)

To put it in perspective I decided to have a crack and see what an equivalent function would look like in Ruby. After a bit of mucking around I came up with this:

1 [1,2,3,4,5].zip([10,9,8,7,6]).map{|x,y| x * y}

I'm sure someone could probably come up with something nicer as there is more than one way to skin a cat in ruby (I haven't tried, honest - ..to skin a cat that is). Regardless I think we should all sit back and appreciate the aesthetics of the lisp map.

Tuesday, September 18, 2007

Meta-Meta-Meta Programming

Now this isn't particularly clever or something you should even really consider doing but i've wasted my time so now you don't have to :). L and I got a lovely new bookshelf a few weeks ago and I noticed my beloved copy of The Pragmatic Programmer eyeing me off in the corner, so I started to thumb through it.

The astute amongst you that have read it would remember their example on meta-programming that uses Perl to take an ordinary text file and generate Pascal and C source code from it. I thought i'd update it a bit and use Ruby to generate Java, and, well Ruby.

I did it in a similar way to the prag guys i.e. the "proper" way but then I got a little bit curious about the notion of source that generates source that generates source. Plus, I hadn't really dug into Ruby meta-programming and this seemed like something harmless to play with.

Seeing we all love Shoes, let's have a look at a file called Shoe.txt that in pretty much plain English, models a shoe.


1 C Shoe
2 M   brand          String        
3 M   colour         String        
4 M   size           int        7    
5 M   isTrendy       boolean    true    
6 M   scent          Scent        
7 E

For clarification, the beginning of each line identifies what it is modelling:

C is the name of the class
M is a member of the class, specifying it's name, type and default value
E signals the end of the class.

Pretty straight forward, eh.


So how do we turn that into:

Shoe.Java


 1 class Shoe {
 2   private String brand = null;
 3   public String getBrand {
 4     return brand;
 5   }
 6   public void setBrand(String brand)
 7     this.brand = brand;
 8   }
 9
10   private String colour = null;
11   public String getColour {
12     return colour;
13   }
14   public void setColour(String colour)
15     this.colour = colour;
16   }
17
18   private int size = 7;
19   public int getSize {
20     return size;
21   }
22   public void setSize(int size)
23     this.size = size;
24   }
25
26   private boolean isTrendy = true;
27   public boolean getIstrendy {
28     return isTrendy;
29   }
30   public void setIstrendy(boolean isTrendy)
31     this.isTrendy = isTrendy;
32   }
33
34   private Scent scent = null;
35   public Scent getScent {
36     return scent;
37   }
38   public void setScent(Scent scent)
39     this.scent = scent;
40   }
41
42
43 }



and Shoe.rb?


1 class Shoe 
2   attr_accessor :brand, :colour, :size, :isTrendy, :scent
3   def initialize 
4     @colour = nil
5     @size = 7
6     @isTrendy = true
7     @scent = nil
8   end
9 end

Well, there is the smart way i.e. the pragmatic way, or there is the meta-meta-meta programming way.

language_generator.rb


 1 langs = %w(ruby java)
 2 class LangGen 
 3 end
 4
 5 langs.each do |lang|
 6   LangGen.class_eval <<-LETS_DANCE
 7     $first = true
 8     $init = ""
 9  
10     def #{lang}_class_start(name)
11       out = "class " + eval(\"name.chomp\") + ' '
12       out << "{" if '#{lang}' == 'java'
13       out << "\n"
14       out  
15     end
16   
17     def #{lang}_class_end
18       out = ""
19       out << "\n}" if '#{lang}' == 'java'
20       if '#{lang}' == 'ruby'
21         out << $init
22         out << "  end"
23         out << "\nend"
24       end
25       out
26     end
27   
28     def #{lang}_members(name, type, value)
29       out = ""
30       if '#{lang}' == 'java'
31         value ||= 'null'
32         out << "  private " + eval(\"type\")+' '+eval(\"name\")+' = '+
33                   eval(\"value\")+";\n"
34       end
35   
36       out << "" if '#{lang}' == 'ruby'
37       out
38     end
39   
40     def #{lang}_accessors(name, type, value)
41       accessors = ""
42       if '#{lang}' == 'java'
43         accessors << "  public " + eval(\"type\")+
44                         " get"+eval(\"name.capitalize\")+" {\n"
45         accessors << "    return "+eval(\"name\")+";\n"
46         accessors << "  }\n"
47         accessors << "  public void set"+eval(\"name.capitalize\")+ '('+
48                         eval(\"type\")+ ' '+ eval(\"name\")+")\n"
49         accessors << "    this."+eval(\"name\")+" = "+eval(\"name\")+";\n"
50         accessors << "  }\n\n"  
51       end
52       if '#{lang}' == 'ruby'
53         value ||= 'nil'
54         if $first
55           $init << "\n  def initialize \n"
56           accessors << "  attr_accessor :" + eval(\"name\")  
57         end
58         accessors << ", :" + eval(\"name\") unless $first
59         $init << '    @'+ eval(\"name\")+' = '+eval(\"value\")+"\n" unless $first
60         $first = false
61       end
62       accessors
63     end    
64  
65   LETS_DANCE
66 end
67
68 gen = LangGen.new
69 langs.each do |lang|
70   File.open('Shoe.txt').each do |line|
71     if line =~ /^C/
72       print gen.send("#{lang}_class_start".to_sym, line.gsub(/^C\s+/,''))  
73     end
74     print gen.send("#{lang}_class_end".to_sym) if line =~ /^E/ 
75     if line =~ /^M\s+(\w+)\s+(\w+)\s+(\w+)?/
76       name, type, value = $1, $2, $3
77       print gen.send("#{lang}_members".to_sym, name, type, value)
78       print gen.send("#{lang}_accessors".to_sym, name, type, value)
79     end
80   end
81   printf("\n"+'*'*50 + "\n")
82 end

Tuesday, August 28, 2007

"Ruby off the Rails?" quid pro quo

I came across Ruby off the Rails? by Paul Turner and he issued a challenge at the end of his post, and I bit :)

Read it here (including the comments) then come back for my some-what larger response.

Begin communique:

I agree with you that Microsoft adding more and more languages to the framework is a bad idea. J# anyone? . Their ploy is to lower barrier to entry, so what you end up with is some developers using anything but C#, because, well why bother learning it if you don't have to. Have you tried proposing C#.NET to a VB developer when they can use VB.NET? I have, it's not fun. It might just be me but I question the maintainability of an application written in 7 different languages.

To answer your question:

Read the following with the caveat of using the "right tool for the right job". Obviously if you are targetting a windows desktop app, it is hard to trump WinForms. However i think .NET is licked in every other category.

  1. Portability (Mono still doesn't implement the full .NET 2.0 API)

  2. Expressiveness of language:

  3. 1    for (int i = 0; i < 5; i++) {
    2      Console.Out.Write("c# loop ");
    3    }

    vs.

    1 5.times { print "ruby loop " }

  4. Cost $$$$$$$$$$$$$$$

  5. MSDN documentation is, well crap, contrast:

  6. http://msdn2.microsoft.com/en-us/library/system.io.file(vs.85).aspx
    and
    http://java.sun.com/javase/6/docs/api/java/io/File.html

  7. Closed source / Open standard


I can safely say that you are the first Microsoft proponent that I have come across that knows the difference between open-source and open-standard :). Some people claim that .NET is open-source. No; it's a huge difference. Java is open-source: http://www.sun.com/2006-1113/feature/. Ruby is open-source: http://www.ruby-lang.org/en/LICENSE.txt. But you hit the nail on the head C# is an open standard: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf.

This might not be a kicker for a lot of people, but it is to me. I like being able to delve into source and see what's going on and change it to suit me. This is how i found a bug in the Sun implementation of the CachedRowSet, i thought i was going insane but i was able to get in there and figure why it wasn't working and then report it. Kudos for the open-standard, but one up it and go the whole hog.

I was also working as a government employee when I discovered that bug. If the source hadn't been open I would not have been able to temporarily patch it and get on with what I was doing.

I also have to disagree about the 'purity' of keeping SQL code in SQL Server. It's fine if you have resigned yourself to paying for SQL Server subscriptions the rest of your life. But i'd like to bet on a bit of flexibility and write ANSI compliant SQL that i could quite easily drop into Postgres or any other db for that matter. Ofcourse there are extenuating circumstances (performance reasons and the like) to dabble in sql server extensions, but it is a rarity. I wish i also had the energy to go the age old 'dynamic sql vs stored procedures' argument with you too :)

Rather than bemoan the fact that is IronRuby, maybe have a look at it, who knows you might give up your C# day job :)

Friday, August 17, 2007

Automating SVN stuff with Ruby

Ahoy hoy.

The other day at work I was looking at upgrading an SVN installation at an off-site and came across a good 70 odd repositories dumped to backup. Rather than take the typical windows muppet route and unzip each one, create an svn directory and import them manually, I scripted the whole thing.

Ruby lends itself to this sort of work. I've used it in the past to automate daily and weekly backups of another repository so I knew it would be pretty easy to sort this out. The best part is it didn't take more than 15 minutes from go to whoah. Here it is in it's entirety, it's not a shining example of best practice, nor does it profess to be, but it does what it's supposed to do.



 1 ################################################################################
 2 #
 3 # SVN importer
 4 #
 5 # rubyzip must be installed: gem install rubyzip
 6 #
 7 ################################################################################
 8
 9 require 'zip/zipfilesystem'
10
11 SVN_REPOS_URL = "http://paleale:8090/svn-repos/"
12 SVN_REPOS_PATH = "C:/svn-repos"
13 BACKUP_DIR = "//sparkling/SVNbackups/"
14 UNZIP_DIR = "C:/Documents and Settings/dan/My Documents/"
15
16 # unzip all the dump files into the UNZIP_DIR
17 # we make the assumption that the dump file inside the zip shares the same name
18 # i.e. web_indicator.dump.zip has web.indicator.dump inside it
19 Dir["#{BACKUP_DIR}*/*.zip"].each do |zipFile|
20   dumpFile = zipFile.gsub(/^.*\//,'').gsub(/\.zip/,'')
21   Zip::ZipFile.open(zipFile) do |unZip|  
22     unZip.extract(dumpFile, UNZIP_DIR + dumpFile)
23   end 
24 end  
25
26 # create svn directories for each dumpFile, load into SVN then delete the file
27 Dir.glob("#{UNZIP_DIR}*.dump").each do |dumpFile|
28   projName = dumpFile.gsub(/^.*\//,'').gsub(/\.dump/,'')
29   cmd = %{svn mkdir -m "automated: creating project structure from import" } 
30   cmd << SVN_REPOS_URL << projName
31   %x"#{cmd}"
32   cmd = "svnadmin load --parent-dir #{projName} #{SVN_REPOS_PATH}"
33   cmd << %{ < "#{dumpFile}"}
34   %x"#{cmd}"  
35   File.delete(dumpFile)
36 end

Wednesday, August 1, 2007

Pander to familiarity

The first step of getting out of the familiarity trap is to feel confident in sizing up what else is on offer.

A simple set of criteria as defined by Sebesta in 'Concepts of Programming Languages' includes:

  1. Readability

  2. Writability

  3. Reliability

The invisible 4th criteria I belive is Familiarity. Unfortunately, in reality I believe that familiarity takes precedence for the majority. You might argue that familiarity is a construct of readability and writability but I disagree. It may be a fuzzy line; but to me something isn't purely readable and writable just because you've twisted your mind to think that way.

For example:

1 for (int i = 0; i < 5; i++) {
2   Console.Out.Write("hi ");
3 }


That looping construct makes sense to a veritable legion of programmers (pick your printf or println statement). Is it readable and writable because you've done it a gazillion times or is it inherently simple?

Contrast that with:

1 5.times do
2   print 'hi '
3 end


If you pulled a layman off the street and showed them both I could take a guess at which one would be considered more readable.

In Steve Yegge's (in)famous next big language post his number #1 rule for the next triumphant language is 'C like syntax'. I can tell he feels oh so dirty saying that, but 'you gotta give the programmers what they want'.

Things like orthogonality, control structures and data types don't seem to rank as highly. Whatever happened to the best tool for the job? If I know there is something out there that works I don't care whether it's functional, applicative, imperative, logical, whatever. It's just a side-effect. If you have a brain in your head, you can work it out.