Apache and AppleScript
Pages: 1, 2
Step 1: Initialize the Access File
In order to generate the comments for our web site, the very first thing we'll have to do is get a reference to the document root--we never move above this folder, only below. This folder could be /Library/WebServer/Documents, ~/Sites/, or, if you have a virtual host enabled on your machine, pretty much any directory in the filesystem. With AppleScript, specifying a reference to a folder at runtime can be done in one of two ways:
- By dragging a folder (or a series of folders) onto the droplet from within the Finder.
- By making a call to the
choose folderfunction from within your script.
To allow both methods to be used, we will save the script as an application and create a subroutine that is invoked from each method handler, named appropriately enough, addComments:
(* invoked when files are dragged onto the script
within the Finder *)
on open (myfiles)
tell application "Finder"
repeat with i in myfiles
my addComments(i)
end repeat
end tell
end open
(* invoked when the script is opened from the Finder
(either double-click, or via Script Menu *)
on run
set myfile to choose folder
my addComments(myfile)
end run
Figure 5. Dropping our web document folder onto the script's droplet for processing
The meat and potatoes of the script is contained within the addComments subroutine, although it relies on several other subroutines for assistance:
on addComments(myfile)
set myPOSIX to (POSIX path of myfile as string)
(* check to see if this is a folder*)
if (my isFolder(myfile)) then
tell application "Finder"
(* make sure there's an access file in this
directory *)
set accessfile to my prepareAccessFile(myPOSIX)
(* add a description for the parent folder *)
set myparent to parent of myfile
my addDescription(accessfile,"..",comment of myparent)
(* add a description for every file/folder... *)
set myfiles to items of myfile
repeat with i in myfiles
set m to i as alias
(* add an entry for it in the access file *)
my addDescription(accessfile,name of i,comment of i)
(* and repeat this process with it if its a subfolder *)
if (my isFolder(m)) then
my addComments(m)
end if
end repeat
end tell
(* if it's not a folder, then ignore it*)
end if
end addComments
The addComments routine is invoked with every file that is contained in the document tree of our web site. And with each parameter that is passed to the routine, in addition to the file's parent directory (denoted by ..), an AddDescription directive is added to the Apache access file residing in the file's directory. But before the description is added to the access file, we must ensure that there is an access file in place. Which turns out to be more complicated than it sounds.
Preparing the access file can take one of two courses:
- If the access file exists (whether it was manually created or through a previous execution of this script), then all of the
AddDescriptiondirectives that it contains will have to be deleted. However, because there is the possibility that it may contain other directives, all other directives will have to remain untouched. - If the file doesn't exist, then a blank file will need to be created so that it can be populated.
This step is where the script becomes more complex than your run-of-the-mill AppleScript:
(* checks to see if a folder has a ACCESS_FILE in it;
if yes, then formats it to start from scratch,
if not then creates one *)
on prepareAccessFile(myPOSIX)
(* get the path name that we will be testing *)
set myaccess to (myPOSIX as string) & ACCESS_FILENAME
set accessposix to myaccess as POSIX file
(* checks to see if the access file already exists *)
if (my hasAccessFile(myPOSIX)) then
set myScript to
("perl -pi -e \"s/^(.*AddDescription.*)//sg\" ") &
(quoted form of POSIX path of accessposix as string)
do shell script myScript
else
(* if no access file exists, then make one *)
my makeAccessFile(myPOSIX)
end if
return accessposix
end prepareAccessFile
The first step is to determine whether or not an access file exists, which is accomplished via the hasAccessFile routine. This routine returns a Boolean indicating whether or not an access file already exists in the directory:
(* returns true if the given folder has an ACCESS_FILE in
it *)
on hasAccessFile(posixFolder)
set myListing to do shell script "ls -a " &
quoted form of posixFolder
return (myListing contains ACCESS_FILENAME)
end hasAccessFile
The traditional way of doing this would be to tell the Finder to get a list of all of the items in the folder and see if that list contains the access file. However, because our access files are hidden by the Finder (that is, they begin with a .), the Finder won't include it in the list of files. This can be remedied by using one of the various hacks (such as this one) to instruct the Finder to list all files in a directory. But to avoid such trickery, we'll use the shell to leave the Finder's preferences intact. ls, of course, is the UNIX command to list all files in a directory, and the -a flag will tell the command to include all files in the listing, even hidden ones.
If the access file exists, then all AddDescription directives contained within it must be deleted, as they may contain descriptions that are out of date. There are several ways of accomplishing this step, each with its own pros and cons:
- AppleScript has the capability to manipulate the contents of text files (through reading and writing), but it is by no means an easy task to do. Third-party extensions (also known as "osax" for "Open Scripting Architecture eXtension") that add regular expression capabilities to simplify the task are available, but require installation and often have licensing fees.
- Using the AppleScript functionality of a text editor (such as BBEdit) will make our task easier, but again, this method will require the application to be installed on the computer where the script will run.
- Another way of accomplishing this step is to make use of OS X's UNIX foundation; command-line tools are typically built solely to manipulate files and text. The only downside to this method is the complexity in creating the UNIX command.
After all of the discussion earlier in the article regarding AppleScript and UNIX, it should come as no surprise that the third method was chosen: it is the only way to accomplish the task at hand without requiring some kind of extension, whether it be a scripting addition or application.
The following statements will do exactly what we need:
set myScript to
("perl -pi -e \"s/^(.*AddDescription.*)//sg\" ") &
(quoted form of POSIX path of accessposix as string)
do shell script myScript
For those in the audience that don't happen to be Perl hackers--I happen to fall into this category myself--the above command will use the Perl interpreter to scan through the access file and remove all lines that contain the string AddDescription (as shown in Geoffrey Bradwell's article, "FMTYEWTK About Mass Edits In Perl"). We now have a fresh access file devoid of AddDescription directives, assuming it existed before running our script.
But what happens if it doesn't exist? Well, we're going to have go ahead and create it, and that's what the makeAccessFile routine takes care of:
(* creates an ACCESS_FILE in the given directory *)
on makeAccessFile(posixPath)
(* build the path where the file should be located *)
set myquoted to quoted form of posixPath
set myquoted to (characters 1 thru
((length of myquoted) - 1) of myquoted)
set mypath to
(myquoted & ACCESS_FILENAME & "'") as string
(* create the file *)
do shell script "touch " & mypath &
"; chmod 744 " & mypath
(* ensure the proper permissions are set *)
do shell script "chmod 744 " & mypath
(* and add a comment indicating when it was generated*)
set myScript to "echo \"" & ACCESS_COMMENT &
"\" >> " & mypath
do shell script myScript
end makeAccessFile
This routine takes a similar route to creating the access file: instead of telling the Finder to create the file, the UNIX command touch is used, and then the proper permissions are set on the file. The last call to do shell script merely adds a comment to the top of the file to mark when and how the file was created.
There are only two things left to do: add the actual AddDescription directives to the access file for each file, and then recursively invoke addComments with any sub-folders of the current folder. And thankfully, both steps are much simpler than the code we just walked through.
Steps 2 and 3. Create the AddDescription Directive and Recurse
The next block of code in the addComments routine is the following:
repeat with i in myfiles
set m to i as alias
my addDescription(accessfile, name of i, comment of i)
if (my isFolder(m)) then
my addComments(m)
end if
end repeat
The above block accomplishes the first and second steps of our algorithm--add the comments for the current file and then dig down into it if it's a folder. The addDescription routine is shown below:
(* adds an AddDescription directive into the
specified access file *)
on addDescription(accessposix, fileName, comments)
set mypath to POSIX path of accessposix
set myquote to quoted form of mypath
if (comments is not equal to "") then
set myScript to "echo \"AddDescription \\\"" &
my urlencode(comments) & "\\\" \\\"" &
my escape(fileName) & "\\\" \" >> " & (myquote)
set a to do shell script (myScript)
end if
end addDescription
Again, we are using a UNIX command to add the directive to the end of the access file; the name of the file is escaped (in case it contains a quote, backslash, or another misbehaving character) and the comment is URL-encoded.
Several sections of the script weren't covered in this article (such as the urlencode and urlescape routines for embedding the filename and its comment within the UNIX command); rest assured that they aren't directly involved in the script's functionality.
Putting the Script to Use
We've walked through the code for the script; now let's see it in action. The following files all reside in the same folder within my web document root (/Library/WebServer/Documents), and each have a comment applied to them through the Finder's Get Info window:
Figure 6. Setting comments for the files in a folder of our web document folder
And the following is the index page generated by Apache, which uses the information covered in my previous article "Mo' Betta Indexes" and the access files generated by the script:
Figure 7. The result of all of our hard work. Click for a larger version.
Although a lot of effort has been expended in to create this one index page, we can now leverage our toolset to create as many pages as we want and have most of the content generated for us, not by us. All we have to do is click a couple of buttons here and there.
Final Thoughts
In this article, we put AppleScript to use in a way that it was not originally designed to be used. While many developers trained in high-level languages such as Java and C despise AppleScript because of its verbose and unstructured syntax, it still plays an integral role within OS X. And with the release of Automator in the next version of OS X, scripters are going to be able to build even more complex tools to suit their needs.
David Miller is combining his passions of photography and working on the web at iStockphoto; when not hacking away with a text editor and a few web browsers in hand, he can be seen huddled over his laptop tweaking levels and curves for his freelance photography. Keep track of David's latest projects over at his home on the web.
Return to MacDevCenter.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 7 of 7.
-
Finder comments
2005-03-08 15:27:51 sjk [Reply | View]
Interesting article, David. Just wanted to mention that Finder comments are stored in .DS_Store files, not resource forks. And Finder label metadata is stored within the filesystem.
-
Glaring Error
2005-02-12 01:24:47 has01 [Reply | View]
"""AppleScript is a language used to accomplish what traditional scripting languages are unable to do: exchange information between applications on a higher level than is possible through plain-text pipes on the command line;"""
Not so. There are plenty of traditional scripting languages with varying levels of Apple event support. Offhand, here's the ones I can think of:
- Perl has long had well developed application scripting support in the shape of Mac::Glue (download) (perl.com article).
- Python also has very good application scripting support that easily gives AppleScript a run for its money; see appscript (note: self-link).
- Late Night Software do a JavaScript OSA component.
- Tcl and Ruby both have basic Apple event support.
- And UserTalk is the granddaddy of them all - AppleScript included - being the first ever scripting language ever to support OSA (ch. 32 of Matt Neuburg's Frontier book).
For example, here's how to get a Finder comment using MacPython+appscript:
#!/usr/bin/pythonw
from appscript import *
path = '/Users/NAME/your/path/here'
comment = app('Finder').items[path.replace('/', ':')].comment.get()
HTH
has
p.s. Your apachecomments.zip file is broken. Old-style applets store their script in the resource fork, so you'll need to use a compression format that preserves this. -
Re: Glaring Error
2005-02-12 13:04:30 David Miller |
[Reply | View]
Yes, AppleScript is only one implementation of the OSAScript specification, which is why the shell command to execute OSAScripts is calledosascriptinstead ofapplescript. Can you use JavaScript to write OSAScripts that work interact with AppleEvents ? Absolutely. So to be completely correct I should've used the term "OSAScript-aware languages and extensions can be used to accomplish..." instead of "AppleScript can be used to accomplish..." My bad.
The projects that you mentioned above are exactly what I was referring to when I wrote "However, the HFS(+) filesystem also uses resource forks to store metadata for files, which 'traditional' languages can't access without special methods."
There comes a point where a line has to be drawn that separates what will and won't be covered in a piece of writing; I decided to gloss over those details because I didn't feel they were necessary to the point that I was trying to get across at that stage in the article: that AppleEvents provide a much richer method of passing data back and forth than plain-text streams through standard input/output.
To be really picky, it should be stated that it is ECMAScript that can be used to write OSAScripts, since JavaScript, which technically only lives within web browsers, is merely one implementation of the ECMAScript standard--Flash's ActionScript being another. But is anything gained by going into that extra level of detail? Not really; certainly not enough to justify including the additional information in your comment. Which happens to be the line that you chose to draw.
I would like to thank you for pointing out that information in the Comments section, as it provides a way for the readers (and myself!) to find out more information relating to the topics covered in the article.
As for the broken zip file, it was created using Panther's "Create Archive" command from within the Finder. If you expanded the archive with another tool (such as Stuffit Expander or WinZip), then yes, the resource fork may not be attached to the resulting file. To work around this problem, I've posted a copy of the script in SIT format to:
http://www.fivevoltlogic.com/code/applescript/apachecomments/apachecomments.sitx
My apologies. -
Re: Glaring Error
2005-02-12 15:14:09 has01 [Reply | View]
"""So to be completely correct I should've used the term "OSAScript-aware languages and extensions can be used to accomplish...""""
In fact, interpreted/compiled languages don't even need OSA support to script applications, just a bridge to the Apple Event Manager's C-based API. e.g. I always use the standard MacPython framework and pythonw command-line interpreter; no osascript involved.
There are basic OSA language components for Perl, Python and others, but all this actually gives you is the ability to edit and run scripts in OSA-based editors like Script Editor and attach them to applications that support attachability.
Extremely cool technologies, btw, though often not very well explained or documented by Apple which I think causes a lot of the confusion.
"""The projects that you mentioned above are exactly what I was referring to when I wrote "However, the HFS(+) filesystem also uses resource forks to store metadata for files, which 'traditional' languages can't access without special methods.""""
I wouldn't describe a quick trip to CPAN or PyPI to install a new module package as uncommon or unusual in any way, but that's by-the-by. In any case, it was another paragraph I was commenting on - see below.
BTW, can't mind where comments are stored in OS X, but it's not individual files' resource forks (OS9 used the Desktop DB). Though the Carbon File Manager API includes functions for accessing comments if you don't want to script Finder.
"""I decided to gloss over those details because I didn't feel they were necessary to the point that I was trying to get across at that stage in the article: that AppleEvents provide a much richer method of passing data back and forth than plain-text streams through standard input/output."""
Absolutely; Apple event-based IPC is a great technology that doesn't get nearly as much attention as it deserves, so it's always a welcome sight when O'Reilly covers it. However, your article didn't so much 'gloss over' (which is fine) as say it couldn't be done:
<blockquote>"Through [AppleScript] you can gain access to information that is inaccessible to the cross-platform scripting languages (or even high-level languages such as Java and C's derivatives) mentioned above."</blockquote>
Which is an error, not an omission, as any language that can talk C can access this info - and quite a few already provide low-, mid- and/or high-level bridges for this purpose. Hence the correction. :)
"""As for the broken zip file, it was created using Panther's "Create Archive" command from within the Finder [...] My apologies."""
No worries. I think Apple in their forward charge sometimes forget their more poverty-stricken users can't always keep right up. Server seems down right now but I'll try again later, thanks.
Cheers,
has
-
running AppleScript CGIs
2005-02-12 00:28:17 tkokesh [Reply | View]
I wrote an AppleScript CGI a few years back, but when I tried to resurrect it the other day, I wasn't able to get Apache to recognize my script and run it. Any idea how to do this on Mac OS X? -
running AppleScript CGIs
2005-02-16 21:56:09 David Miller |
[Reply | View]
Unfortunately (or, fortunately, depending on how you look at it), OS 9's web sharing and the Apache web server that powers OS X's web sharing are two completely different beasts. You'd be better off re-writing your script in a UNIX scripting language such as PHP or Perl to get things done in OS X. Unless, of course, someone has written an AppleScript-CGI bridge for Apache :). -
running AppleScript CGIs
2005-02-17 06:39:38 has01 [Reply | View]
> Unless, of course, someone has written an AppleScript-CGI bridge for Apache :).
Not used it myself, but acgi dispatcher looks like the ticket. HTH





