Tab Dumping with AppleScript and back to Python

Rock

Goal: Iterate through all my (OS X Safari) browser windows and make a list of titles and urls which is then placed in the clipboard ready to be pasted into an email or blog post.

This is an update to Tab Dumping in Safari. That still works well as the basis for extending any Cocoa-based application at runtime, but it relies on SIMBL, which while it is a great bit of code, essentially is abusing the InputManager interface. Some developers and users shun such hacks, and at least one Apple application checks for them at startup and warns you from using them.

I have been running the WebKit nightlies, which are like Safari, but with newer code and features (most importantly to me right now, a Firebug-like developer toolkit). WebKit warns at startup that if you’re running extensions (such as SIMBL plugins) it may make the application less stable. I was running both Saft and my own tab dumping plugin, and WebKit was crashing a lot. So I removed those and the crashes went away. I miss a handful of the Saft extensions (but not having to update it for every Safari point release), and I found I really miss my little tab-dumping tool.

I toyed with the idea of rewriting it as a service, which would then be available from the services menu, but couldn’t figure out how to access the application’s windows and tabs from the service. So I tried looking at Safari’s scriptable dictionary, using the AppleScript Script Editor. Long ago, John Gruber had written about the frustration with Safari’s tabs not being scriptable, but a glance at the scripting dictionary showed me this was no longer the case (and probably hasn’t been for years, I haven’t kept track).

I am a complete n00b at AppleScript. I find the attempt at English-like syntax just confuses (and irritates) me no end. But what I wanted looked achievable with it, so I armed myself with some examples from Google searches, and Apple’s intro pages and managed to get what I wanted working. It may not be the best possible solution (in fact I suspect the string concatenation may be one of the most pessimal methods), but it Works For Me™.

In Script Editor, paste in the following:

set url_list to ""
-- change WebKit to Safari if you are not running nightlies
tell application "WebKit"
  set window_list to windows
  repeat with w in window_list
    try
      set tab_list to tabs of w
      repeat with t in tab_list
        set url_list to url_list & name of t & "\n"
        set url_list to url_list & URL of t & "\n\n"
      end repeat
    on error
      -- not all windows have tabs
    end try
  end repeat
  set the clipboard to url_list
end tell

I had to use AppleScript Utility to add the script menu to my menu bar. From there it was easy to create script folders that are specific to both WebKit and Safari and save a copy of the script (with the appropriate substitution, see comment in script) into each folder. Now I can copy the title and URL of all my open tabs onto the clipboard easily again, without any InputManager hacks.

I had some recollection that is a way to do this from Python, so I looked and found Appscript. I was able to install this with a simple easy_install appscript and quickly ported most of the applescript to Python. The only stumbling block was that I couldn’t find a way to access the clipboard with appscript, and I didn’t want to have to pull in the PyObjC framework just to write to the clipboard. So I used subprocess to call the command-line pbcopy utility.

#!/usr/local/bin/python
from appscript import app
import subprocess
tab_summaries = []
for window in app('WebKit').windows.get():
    try:
        for tab in window.tabs.get():
            name = tab.name.get().encode('utf-8')
            url = tab.URL.get().encode('utf-8')
            tab_summaries.append('%s\n%s' % (name, url))
    except:
        # not all windows have tabs
        pass
clipboard = subprocess.Popen('pbcopy', stdin=subprocess.PIPE)
clipboard.stdin.write('\n\n'.join(tab_summaries))

The remaining hurdle was simply to put the Python script I’d written into the same Scripting folder as my AppleScript version. For me this was ~/Library/Scripts/Applications/WebKit/. When run from the scripts folder, your usual environment is not inherited, so the #! line must point to the version of Python you are using (and which has Appscript installed). You should also make the script executable. Adding .py or any other extension is not necessary.

Overall, while I found AppleScript to be very powerful, and not quite as painful as I remembered, I found the Python version (warts and all) to be easier to work with. Combined with the fact that the script folder will run non-Applescript scripts, this opens up new worlds for me. I have hesitated in the past to write a lot of SIMBL-based plugins, tempting though it may be, because they are hacks, and they run in every Cocoa-based application. But adding application-specific (or universal) scripts, in Python, is pure, unadulterated goodness.

Processing Playground

Kudzu 3

There is a lot of interesting stuff happening in Javascript land these days, even to the point of other languages targetting the browser as a runtime, but running on top of Javascript. You can run Scheme right in the browser, and by now everyone has probably heard of Objective-J (open-source coming soon), an Objective-C-like language used by 280 North to create their 280 Slides web application, inspired by Apple’s Keynote.

Since my last post about Processing, John Resig managed to port most of Processing to Javascript, so it is easier than ever to get started. Now instead of having to download the Java-based runtime, you can create Processing animations in your browser, within the limitations that it only targets very recent, beta browsers (Firefox 3, Opera 9.5, WebKit nightlies, no version of IE) and that not all of Processing is supported (no 3D, for instance, and my example from the earlier post does not run). Still, it is interesting and a lot of fun to play with. My seven-year-old son is fascinated with computer programming and looking to move beyond Scratch, so as part of that I stuck all the basic examples from Mr. Resig’s site into one page, with a menu to select them, and a button to run them. And I made them editable. You can write entirely new code too, of course, but the examples can help for getting started. I hope folks enjoy it.

Processing Playground

Of course, what my kids really want is a version of Scratch that we can extend to add some more features of our own. Scratch has been open-sourced, so we could possibly extend it, but it is built on Squeak Smalltalk, and I’ve never been very good at Smalltalk. Instead, I am porting it to Javascript. It is still in the early stages, but I’m making steady progress in my hour or so I have to code each evening, and my kids are eager to use it, so they keep me motivated and focussed.

Apologies

Mandala

I wanted to apologize for spamming Planet Python (and anyone else that happens to subscribe) with old posts recently. After spending many of my free hours for the past year noodling around with my weblog software (and the myriad of problems getting anything to run persistently on Dreamhost), I finally gave up and switched to WordPress. I’ve imported my old blogs for the sake of continuity (from Blogger and my own custom weblog, but not going back to Manila yet), and mapped the old URLs with redirects, so things should be fairly transparent to the casual observer.

A couple of good things came from my time wrestling with my own software. I learned a lot more about WSGI. I got to evaluate TurboGears, Django, web.py, Pylons, and raw WSGI. I learned more about Atom syndication and AtomPub. And while I was doing all that, WordPress got semi-decent Atom 1.0 support and also began to support tagging: two of the reasons I was rolling my own. It’s still dog-slow, but I think that may have more to do with how DreamHost is running it (as a CGI) than WordPress itself. And since I’m running several other WordPress blogs for family members and friends, at least I only have one system to fight with rather than two.

Another good thing that came out of switching to WordPress, and one of the main reasons I had ripped my old blogging system apart to rebuild it it as a server-side tool, is that my blog supports comments now. I have spent the past year doing a bit more than just twiddling with blogging software, and I hope to have some of my more interesting experiments and some tutorials to post soon. Let me know what you think of it.

The importance of visual programming

Kudzu flower manipulated with NodeBox

Python has a well-earned reputation for being easy to use and to learn, at least for people who have learned programming in other languages first. Lately my kids have been very interested in programming, and I’ve found that Python doesn’t come as easily to 6-11 year olds as it does to adult programmers.

So I see two approaches to this problem, if it is a problem. One, let them use other languages than Python. Two, find (or make) ways for Python to be more approachable. Let’s look at both of these.

Scratch Screenshot

Scratch

For languages other than Python, there are some really good choices. The best language for getting kids interested and up to speed quickly that I’ve found is Scratch, which is a visual, drag-and-drop programming language for games and animations based on Squeak Smalltalk. Scratch allows kids to program without worrying about the syntax, so they can drag blocks together, use the built-in drawing program to create sprites or import images from the library that comes with the environment. They can also share their creations on the web, projects get automatically converted to Java applets when shared on the Scratch website, and the kids can vote for and comment on each others projects.


Learn more about this project

Scratch is great for learning and for sharing. My seven-year-old can download someone else’s project and generally is able to read it to see what is going on, so it is good for building basic programming literacy. It also has some pretty severe limitations: no user-defined blocks, no return values, no file interaction (so no high scores), no network interaction, no dynamic object creation, the program cannot draw on sprites (only on the background), no string variables or any real string handling. It is a great environment for learning to think creatively within its constraints, but my kids also bump up against its limits pretty quickly.

One option that is often suggested as a step up from Scratch is GameMaker, which apparently is a very nice commercial system that lets kids build games. Besides being commercial, it is also Windows-only, which makes it a non-starter in my household. Scratch runs on both Windows and OS X, with a port to Linux being worked on, but as a Windows-free household, GameMaker is right out.

Quartz Composer Screenshot

Quartz Composer

Another interesting system we’ve been playing around with lately is Quartz Composer. This is a program that comes with Apple’s free Developer Tools. Rather than snap-together blocks, as in Scratch, it is a patch-based environment (there are many of these for working with audio): You drag patches onto a workspace, then wire them up by dragging from the output of one patch to the input of another. Different patches have different numbers of connectors and there are a wide variety of patches to choose from, along with template starter projects for screen savers, audio visualizers, and more. This is a popular tool with VJs, and there is a community around it for sharing custom patches and compositions. A composition can be saved and used in various Cocoa programs that take *.qtz files as plugins, such as the Screen Saver or iChat.

While my seven-year-old can create some effects by playing around with the patches and randomly wiring them up, the system as a whole has been too abstract for him and he doesn’t get it the way he gets Scratch. There is a steeper learning curve to literacy with Quartz Composer.

eToys Screenshot

eToys

One more tool we’ve begun to explore is Squeak/eToys. I have tried in the past to understand the Squeak environment, and to grasp eToys. I was amazed how impenetrable this system, ostensibly designed for children, was to me. A while back I read on Ted and Julie Leung’s respective blogs I read about Squeak: Learn Programming with Robots. This book finally gave me a good starting point for learning Squeak, and an introduction to eToys I could understand. Familiarity with Scratch helps too, since I think Scratch came about in part as a way to make eToys more accessible. So far, while I like the ideas behind Smalltalk, I haven’t been able to muster much enthusiasm for Squeak. It’s always been slow on the Mac (the Macs I have now are finally fast enough to make it bearable), and the UI for it is downright ugly.

I realize there are lots of other visual environments out there we could try. Alice and StarLogo are on our radar, for instance. But that is enough to give a sample of both what is available and the journey my kids and I have taken so far.

Turtles

Turning now to Python. Python has some visual tools built in: Tkinter for building a GUI and the turtle library that is built on top of Tkinter. I actually built a turtle library on top of Tkinter once, not knowing that Python came with one. I added a few things: Turtles could follow mouse clicks, and they looked like turtles, not like triangles. There is also the rather more advanced xturtle library, which is quite cool (also built on top of Tkinter). Besides adding more direct manipulation to turtle drawing (point to move, drag to reposition, etc.) I wanted to have the turtles able to write out the resulting scripts, so kids could learn by modifying those starter scripts. Other projects came along though, and I still haven’t finished either my turtle program or my port of xturtle to run on top of PyObjC on the Mac.

Programming for Artists

Speaking of PyObjC, there have been several tools which take advantage of the powerful graphics capabilities of OS X, using the PyObjC bridge to access Cocoa classes and Quartz graphics from Python. The first of these (that I know of) was DrawBot, by Just van Rossum which is now at version 2.0. The 0.9 version of DrawBot was forked to add additional GUI capabilities, but was not differentiated well from the main DrawBot to avoid confusion. Another fork of DrawBot is NodeBox, which also continues on as a current project today and has gathered an ecosystem to it of artists, designers, and many powerful plugin extensions. All of the DrawBot family of tools are inspired by Processing, which is a similarly stripped-down language for graphics processing. While I use NodeBox extensively, and work with the kids with it (my 11-year-old created the logo for her weblog with it), Processing has its advantages too. NodeBox is focussed on creating art works in PDF or Quicktime movies and focuses on making these easy (including very easy to get started with animation). Processing is built on Java rather than Python, and you can embed the resulting tools as interactive art or games directly in a web page as applets. So while the core languages used by both are nearly identical, they diverge quickly based on their extensions or the desired end result. I recommend looking at the galleries for both NodeBox and Processing to get a feel for what they can do. The key goal of these projects is that they are written for artists, not for programmers.

Another tool which is inspired by DrawBot and Processing, but is a separate, cross-platform project, is Winston Wolff’s MakeBot. Besides being the only tool here which works across Windows and Macintosh, MakeBot has games as the end-goal, and is specifically designed with teaching kids in mind (Winston uses it for his Lunar Repair Yard course in NYC). Winston has also started the Command Blocks project to bring some of the drag-and-drop programming ideas from Scratch into Python (and which I’m hoping to contribute to as well).

Graphics for Physicists

The first tool on our tour which handles 3D is VPython (formerly Visual Python, but changed because there was already a Visual Python project, but it’s good to know the history because it still starts as “import visual”). Like the Processing-inspired tools, VPython is designed for non-programmers, although in this case it is designed for physics students to create simple simulations easily, although the possibilities for games and art projects still exist. I love the simple API of this library, if you want a sphere the code is “ball = sphere()” That gets you a grey unit sphere centered on 0,0,0. If you want it a different size, color, or position you can pass them as arguments to sphere or change the properties afterwards. If there isn’t a window yet, one will be created automatically, but of course you can create a window yourself (or more than one). To give a more concrete example, here is a complete, animated program from the VPython examples:

VPython Screenshot

from visual import *

floor = box(length=4, height=0.5, width=4, color=color.blue)

ball = sphere(pos=(0,4,0), color=color.red)
ball.velocity = vector(0,-1,0)

dt = 0.01
while 1:
    rate(100)
    ball.pos = ball.pos + ball.velocity*dt
    if ball.y < 1:
        ball.velocity.y = -ball.velocity.y
    else:
        ball.velocity.y = ball.velocity.y - 9.8*dt

One of the projects I would like to explore is using Python to generate/work with POV-Ray for more advanced 3D rendering than what I can get with VPython, but a good starting point is that there is an extension to VPython that can export its models to POV-Ray.

Taking the Red PIL

The Python Image Library (PIL) is more or less the standard tool for creating non-interactive graphics, but it has its place in interactive graphics as well. I often rely on it for importing and exporting images from PyGame, and it can be used off-line for creating or manipulation graphics to use in an animation or game. It is perhaps a measure of my geekiness that I often find it easier to write a program to create or manipulate some graphics than I do to figure out how to perform the same task in Photoshop or any of the other half-dozen or so graphics programs I have at hand. Lately NodeBox has been my tool of choice for these scripts, but I still use PIL a lot when I’m working in Linux.

Pygame Screenshot

The Once and Future PyGame

PyGame is a great success story for Python. There are real, polished games created with it, like SolarWolf and Pathological. It is also used for teaching (as in the LiveWires course), and in game creation contests such as Ludum Dare and PyWeek. My own experience with PyGame has been something of a love/hate relationship. On the love side, it is easy to set up, works virtually anywhere (on my tiny Nokia N800, for instance) and is quite good at moving pixels around. On the hate side, it is very low-level, and I either have to learn one (of several possible) libraries that add necessary abstractions for events, input, collisions, etc., or I have to roll my own. It feels like programming the Mac, back in the ’80s. Lately I have been trying to do some work with PyGame targetting the OLPC XO and the aforementioned N800, and have been hampered by the fact that many of the really cool extensions to PyGame rely on OpenGL, which neither of those devices have.

There are other cool Python libraries for graphics: Pyglet, PyOpenGL, PyGame. I’ve written stuff for the kids with PyGame, but I think it is still too much for them to manage themselves. And I’m not cruel enough to have thrown OpenGL (which includes Pyglet) at them yet. Let them think the world is kinder than that for awhile yet.

Overall there is a lot of promise in the world of graphics for Python. There is still plenty of work to be done, too. I guess I’d better get coding.

Tab Dumping in Safari

The Problem

I first saw the term “tab dump” on Dori Smith’s blog, but I immediately recognized the concept. I keep Safari running all the time and with the help of Hao Li’s wonderful extension Saft I keep everything in tabs in one window. Among its many features, Saft will let you consolidate your windows into tabs of one window, and it can save the tabs you have open when you close (or crash) Safari, and re-open them automatically when you start Safari again. What it doesn’t do is give you a list of all the tabs you have open in text format, suitable for blog or email. I don’t currently put tag dumps on the blog because a) I’d feel guilty doing that without adding at least a short comment for each link, which would take too much time, and b) because this isn’t really a link blog, more a place for me to bash out example code and tutorials. At least, that’s how I think of it.

I do however, find Safari teetering on the brink of being unfunctionally slow because I have so many tabs open, and often they’re only open because I want to remember to do something with them later, or come back to them, or some other reminder-type function. So I send myself a tab dump on a more-or-less daily basis. Firefox has tools to help you do this, but I haven’t seen anything for Safari, possibly because you can’t really do it with a Safari plugin, but need to use an InputManager, which is fairly deep magic, and basically a hack, an abuse of the system.

On the other hand, I couldn’t keep using Safari if it wasn’t for Saft, and Saft is an InputManager. Another tool for blocking ads and such (which Saft also does) is PithHelmet, but the interesting thing to me about PithHelmet isn’t that it is a popular ad blocker, but that the Mike Solomon (who wrote PithHelmet) decided to not just make an InputManager, but to make the only InputManager you’ll ever need. You see, PithHelmet itself is not an InputManager, it is a plugin for SIMBL (also by Solomon), which is an InputManager that loads plugins based on the application(s) they claim to support. InputManagers get loaded by every application (Cocoa apps, at least), so you have to be careful you’re in the app you want to modify, and take steps not to break things. SIMBL takes care of the nasty business of being a well-behaved system hack, and your code can assume it is in the right app, because it doesn’t get loaded otherwise.

The Goal

Once I figured out that the only way I was going to get Tab Dumping behaviour into Safari (because Safari tabs don’t play well with Javascript, that turned out to be a dead-end), I decided to try writing an InputManager in Python. SIMBL is open-source, so at first I was looking at the code to see what I need to do to create an InputManager (remember, this is a hack, so Apple doesn’t document it very well). I also read Mike’s essay Armchair Guide To Cocoa Reverse Engineering. What I decided was that, rather than recreate the functionality in SIMBL using Python, I would just create a SIMBL plugin in Python.

Getting started wasn’t too bad, but I found one issue in the above essay that stumped me for awhile. Mike recommends you put your initialization code into a class method load() which gets called after your class is loaded. I don’t know if it is artifact of using PyObjC or what, but my load() method was never getting called. What I did instead was to run the command-line utility class-dump on another SIMBL plugin to see what they were doing. They were using the class method initialize() rather than load and when I switched to that things started working, where by “things” I mean, “I could print to the console to see that my class had loaded.”

The Solution

The next step was to actually do something once I had my code loading into Safari. The tab behaviour of Safari isn’t part of WebKit, so it isn’t documented anywhere. Once again, I used the handy class-dump utility. This is a fabulous tool which will read any Cocoa library, bundle, or application and produce a pseudo-header file showing all the objects and methods defined. I still had to try a few different paths to get to the tab information I wanted, but it was pretty easy, armed as I was with Python and the output of class-dump. Here is the result:

import objc
from Foundation import *
from AppKit import *
class TabDump(NSObject):
    # We will retain a pointer to the plugin to prevent it
    # being garbage-collected
    plugin = None
    @classmethod
    # the following is not strictly necessary, but we only
    # need one instance of our object
    def sharedInstance(cls):
        if not cls.plugin:
            cls.plugin = cls.alloc().init()
        return cls.plugin
    @classmethod
    def initialize(cls):
        app = NSApp()
        menu = app.windowsMenu()
        cls.item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
            'Dump tabs to clipboard',
             'tabdump:',
             '')
        # should be after "Previous Tab" and "Next Tab"
        menu.insertItem_atIndex_(cls.item, 6)
        cls.item.setTarget_(cls.sharedInstance())
    def tabdump_(self, source):
        output = []
        app = NSApp()
        for window in app.windows():
            if window.className() == 'BrowserWindow':
                controller = window.windowController()
                for browserWebView in controller.orderedTabs():
                    output.append(browserWebView.mainFrameTitle().encode('utf8'))
                    output.append(browserWebView.mainFrameURL().encode('utf8'))
                    output.append('')
        self.copyToPasteboard_('\n'.join(output))
    def copyToPasteboard_(self, string):
        pasteboard = NSPasteboard.generalPasteboard()
        pasteboard.declareTypes_owner_([NSStringPboardType], self)
        pasteboard.setString_forType_(string, NSStringPboardType)

As you can see, on my class being initialized, I create a new menu item and insert it into the Windows menu. This could be more robust, by testing menu item names to make sure I’m in the right place, but it works for me, and simple code is more maintainable code. I create an instance of my object and make it the target of the menu item. Pretty basic stuff.

When the tabdump method is called (by selecting the menu item in Safari), it walks through Safari’s window objects (of which there are many) until it finds browser windows, then it extracts the tabbed views from the browser windows to get the titles and URLs involved. When it has collected all the title/URL pairs, it turns it into a big string and puts the string on the pasteboard. Here is where we could be a lot fancier. I’m just putting title/URL pairs, separated by newlines in plain text, because that’s how I mail them to myself. You could easily create Markdown links or any other format here. You could turn them into HTML and put them on the Pasteboard that way. There’s a lot you can do, and the Firefox tool I used to use to do this offered so many options that I was never sure what most of them actually did. Here you can customize the code to do exactly what you need, and keep it simple.

Building the plugin

I haven’t tested this with multiple windows, or with a window with only one tab. It might work, might not. I don’t plan on using it that way, and if I do, it’s easy enough to fix. Now, there is one more thing you’ll need, which is the setup.py script to build it. Assuming you’ve saved the above code as TabDump.py, the following script should be what you need:

'''
    Minimalist build file for TabDump.py
    To build run 'python setup.py py2app' on the command line
'''
from distutils.core import setup
import py2app
plist = dict(
    NSPrincipalClass='TabDump',
    CFBundleName='TabDump',
    SIMBLTargetApplications=[
        dict(
            BundleIdentifier='com.apple.Safari',
            MinBundleVersion='312',
            MaxBundleVersion='420')],
)
setup(
    plugin=['TabDump.py'],
    options=dict(py2app=dict(
        extension='.bundle',
        plist=plist,
    )),
)

In the above file, MinBundleVersion and MaxBundleVersion can keep your code from being loaded if an untested version of the application is running. I have more-or-less dummy values there, don’t treat them as the right thing to do. The SIMBLTargetApplications key holds a list, so if you want your code to load in other applications, add more dictionaries to the list.

Also note that you can build your bundle with python setup.py py2app -A to create a development version (can’t ship it that way) that is all symlinks, so you can edit TabDump.py to make changes without having to rebuild the plugin. If you modify the MinBundleVersion or MaxBundleVersion you will have to rebuild to regenerate the property list (or move the property list to be an external file rather than generating it in setup.py), but that should be an infrequent operation. More importantly, you can put a symlink to your bundle in your ~/Library/Application Support/SIMBL/Plugins/ directory. Then you can make changes to the python code and test it by simply restarting Safari. WARNING: If you have a syntax error in your file, Safari will most likely hang on restart. Just force quit it and check your console for the error to fix.

The Promise

Now, if you’ve followed along with me so far, I’d like to point out a few things that are really freaking cool about this. Item the first: You now have Python running in Safari. Can you think of anything else you’d like it to do while you’re there? I bet you can. Item the second: You can do this in any Cocoa-based application just as easily. Problems in Mail.app? Frustrated by iChat? Just fix it. Take control of your own applications! Make the computer work for you, not the other way around. Item the third: dump-classes gives you the keys to the kingdom. Seriously, the combination of being able to embed Python and get a listing of the objects and methods at will is so powerful that when I got TabDump working late last night and realized what I’d just done (i.e., these three things), I was barely able to get to sleep after that. The possibilities are endless.

If you use this and do something cool with it, please drop me a line and tell me about it. I’m really looking forward to hearing about what kind of cool ways we can push our existing applications.

Correction [2006-08-30]

The class-dump utility rocks, and you should add it to your arsenal of Cocoa tools, along with Python and PyObjC. Since I’ve found it it has already become indispensable for examining existing applications that I want to, er, adjust. Here’s what I’ve learned so far.

First, I want to update my previous post to talk a little bit more about the command-line utility class-dump. This is a fine tool that lets you introspect a Cocoa bundle (plugin, library, or application) and prints out a header file describing all the objects and methods in that bundle. I didn’t mention where to get it, and at BarCamp this weekend I gave some mis-information by telling people it came with Apple’s developer tools, which is not true. I assumed that’s where it came from, because I didn’t remember hearing of it before reading Mike Solomon’s Armchair Guide to Cocoa Reverse Engineering, which refers to classdump without any explanation of where to get it. I tried it, found class-dump worked (tab-completion is your friend), and assumed it came with my system, when in fact I had installed it earlier after reading about it on another blog (I’m afraid I don’t remember where) meaning to try it out, then forgotten about it. So it was there, waiting for me, when I discovered a need for it.

So the truth is, class-dump is a utility written by Steve Nygard. He says it provides the same output as the developer tools command otool -ov, but formatted as a header file. Besides the basic output it can also do various kinds of filtering, sorting, and formatting.
So this is my Tool of the Week (and then some): class-dump. Use it, love it, thank Steve.

« Previous entries Next Page » Next Page »

google

google

asus