December 28, 2007

Turn any shell script into a double-clickable app!

Filed under: ResearchAndDevelopment — Ryan Wilcox @ 1:37 am

So, while this might be very obvious to other people, I found out an interesting thing about bundles today. In short, if you configure your Info.plist correctly, and have the right permissions for everything, it doesn’t matter what you put as you executable.

For example, I have a shell script I want to send someone, but they aren’t computer savvy, so I want it to be a double-clickable app. How do I do this? There are a few tools that help you here: Pashua can give your script a GUI, and can also encase it as a stand-alone app. (‘Cept I don’t really need the GUI part, thank you). DropScript works, but only triggers the shell script when you drop a file onto the app (but I don’t want that either, in my situation).

Looking at Pashua’s stand-alone app, I had a thought, and here’s my method for making app an out of anything.

  1. Start with an empty app bundle.
    You can either build one yourself, or use the one that comes with Pashua’s Sample Stand-Alone app.
  2. Put your shell script into your bundle’s Contents/MacOS/ folder. Make it executable. (For the user and group) with chmod ug+x in Terminal.
  3. Modify Info.plist’s CFBundleName to be the name of your, well, app bundle. Also (and most importantly) modify CFBundleExecutable to be the name of your shell script from step #2.
  4. Double-click you app: your script should have run!

Sadly, if something goes wrong, the system won’t tell you anything. Not a thing. (It’ll just do a half-bounce of the app in the Dock. Or say that the app bundle is damaged.) So you just have to troubleshoot it on your own. Good Luck there.

This shell script of course assumes that all your script needs is right installed on the user’s system. Which might be right in controlled situations. But for (say) Python (or PyObjC) applications, you really should be building with py2app, which bundles up everything the script needs into the app, giving you a stand-alone solution that doesn’t even require that the user have Python (or have the right version of it).

December 10, 2007

Python, Properties and Future Class Changes

Filed under: ResearchAndDevelopment — Ryan Wilcox @ 12:26 pm

For a while now I’ve been thinking about class attributes in dynamically typed languages. I usually view these languages as “less than 50,000 line program languages”. For more lines than that I personally want the type safety and compile checks of a statically typed compiled language (like C++). Granted, a 50,000 line Python program would probably have as much functionality as a half million line C++ program, and with a full suite of unit tests the Python program may be just as reliable and resilient to change (if not more so) than a static compiler checking your work ala C++.

Anyway, because of this “small project” view, and increased experience, most of the time I don’t want to create boilerplate code in my dynamic language class… and accessor methods seem like just that. Ruby has a way around that with its attr_reader/attr_writter, but I do most of my scripting in Python.

But let me add that in languages that lack the ability to retain the same client interface while completely changing the behavior: direct data access to a function call in this case, I’d still write boilerplate getters/setters. Like when I’m writing Applescript modules…. So you could say that Ruby/Python does encapsulation one better: you can change from accessing data to calling a function without knowing it.

The other day I ran across blog post at codefork.com that talks about Python’s take on encapsulation. Python’s approach is very pragmatic: modify the members directly then when you need to – and only when you need to – use Python’s property keyword to make Python call a instance method instead of looking for a data member of that name.

Version 1 of your class could be:

class Person:
def __init__(self):
self.age = None
jeff = Person()
jeff.age = 5 # maturity

Version 2 could be:

class Person(object):
def __init__(self):
self._age = None
def get_age(self):
return self._age
def set_age(self, value):
self._age = value
def del_age(self):
del self._age
age = property(get_age, set_age, del_age)
jeff = Person()
jeff.age = 5

And the codefork article stops there. But there’s more…

Say we plan to deprecate something (the age property), but we want to give our callers time to adjust before we pull the rug out from under them. We take another Python feature to do this: decorators.

To keep this simple we’ll use the most simple decorator: there is a better version of this in the Decorator Library (jump to it)

Version 3:

import warnings
def deprecated(fObj):
warnings.warn("%s is depricated as of version 3. It will be removed completly for version 4" % (fObj.__name__), DeprecationWarning, stacklevel=3 )
return fObj
class Person(object):
def __init__(self):
self._age = None
@deprecated
def get_age(self):
return self._age
@deprecated
def set_age(self, value):
self._age = value
@deprecated
def del_age(self):
del self._age
age = property(get_age, set_age, del_age)
jeff = Person()
jeff.age = 5

warning.warn generates a warning which either could be ignored by the program, sent to standard out, or raise an exception – depending on a value you pass to Python.

The decorator is called automatically by Python before the actual function is called. Think of it like manually calling deprecated(); jeff.get_age() – although its more technically deprecated(jeff.get_age).

Normally decorators make one wonder “so why not just call the one function then the other function and avoid the extra language feature?” In cases like these, where changing client code isn’t an option, or if the function gets called automatically – an age = 5 will call set_age() – decorators make it easy to change stuff around without anybody else knowing.

So we’ve gone through the whole life of an attribute: creation/ “let’s get it working for now”; refinement/ “ok: this needs to happen differently, but lets not tear up everything to do it”; to death/ “ok, we have to remove this, there are better ways now… we need to announce that its going away so people avoid it… then we can pull life support”. We have a way to avoid the broilerplate of tradition encapsulation and also retaining complete control over the class’ destiny – which is what encapsulation is about: preventing internal changes from affecting callers.