I had been experimenting with Ruby/Rake. I took a little while for me to get adjusted to Ruby's syntax. I love closures in Ruby and their effective use in Rake. I was trying if I could do the same in Python. As always, I came up with a very shabby code as the first attempt.
#run target.py
# the global dict to store targets
targets={}
#defining and adding a target
class Target:
def __init__(self,name='',dependencies=[],fn=None):
self.name=name
self.dependencies=dependencies
self.fn=fn
self.done=False
self.success=False
def run(self):
if(not self.done):
self.success=self.fn(self)
self.done=True
return self.success
def describetarget(self):
print self.fn.__doc__
def addtarget(name,dependencies,callable):
objtarget=Target(name,dependencies,callable)
targets[name]=objtarget
# now to add the actual target
def sayHello(target):
'''Prints hello world with the calling targets' name'''
print "hello world from " + target.name
return True
addtarget('sayhello',[],sayHello)
#run the target
targets['sayhello'].run()
As you can clearly see, you would have to first define the function and add it later as the target. For simplicity sake have not written the logic of constructing the dependency tree. You can see that this is nowhere close to writing the same code in Ruby. Consider the following.
Python Code:
def sayHello(target):
'''Prints hello world with the calling targets' name'''
print "hello world from " + target.name
return True
addtarget('sayhello',[],sayHello)
Versus
Rake Code:
task :mytask do |t|
print 'hello world ' + t.name
end
The magic of ruby code is in closures. Python does support closures. Python 2.4 also has a new feature called decorators which can do the same thing. This is what I did in Python using decorators.
#runtargetwithdecorator.py
#global dict to store targets
targets={}
#the target class
class Target:
def __init__(self,name='',dependencies=[],fn=None):
self.name=name
self.dependencies=dependencies
self.fn=fn
self.done=False
self.success=False
def run(self):
if(not self.done):
self.success=self.fn(self)
self.done=True
return self.success
#the target decorator function
def target(name,dependencies=[]):
def decorator(f):
mytarget=Target(name,dependencies,f)
targets[name]=mytarget
return f
return decorator
#implementing the decorator
@target('mytarget')
def MyFunction(target):
print "hello world", target.name
return True
targets['mytarget'].run()
Now consider the following.
Python code:
@target('mytarget')
def MyFunction(target):
print "hello world", target.name
return True
versus
Ruby code:
task :mytask do |t|
print 'hello world ' + t.name
end
Now the python code is a lot more easlier to read than its counterpart in Ruby. Python may not be an effective DSL for build languages but is definitely a lot easier to read and understand than Ruby's code. But you cannot have a empty defaults target defined in python. You should have a valid callable after the @decorator. For example, to my knowledge, you cannot write the following code in python without the following function.
@target('default',['mytarget1','mytarget2'])
def somefn(target):
pass
But you can write the following code in ruby without any closure method.
task :default => [:mytask1,:mytask2]
In spite of Rake's simplicity, I still would prefer writing Python code. I guess it is just my personal preference. No offence to Rake fans. :-)