HeaderDoc 8.8 adds support for AppleScript

I was searching about this in the forums, but, surprisingly, I haven’t found anything. HeaderDoc, Apple’s tool for generating code documentation, has recently added support for AppleScript (and several other languages, including Python, Ruby, shell scripts, etc.) That’s really good news for me, who have been looking for a documentation tool supporting AppleScript for a while, my only choice being NaturalDocs (http://www.naturaldocs.org/) so far. and I hope it is for you, too!

So, in case you’ve missed it, I encourage you to try it (I think you need the latest Xcode)! Here is the documentation:

http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/HeaderDoc/intro/intro.html

In short, you just write code comments between (*! and *), then process your source file with the command-line tool headerdoc2html, to get a beautiful web page documenting your scripts :slight_smile:

Now I’m waiting for a MacScripter’s tutorial :wink:

Here is an example of an AppleScript script documented with HeaderDoc:


(*! @header MyWonderfulScript
@abstract Here goes a short description of the whole file.
@discussion Here goes a long description of the whole file.
Below, some HeaderDoc tags are used
to specify further (meta-)information
(in the output they will not appear here).

@copyright 2011 Myself
@compilerflag -x
@version 1.2.3
*)

(*! @class MyScript
@abstract Here goes a short description of the script.
@discussion Here goes a long description of the script.  HeaderDoc can interpret some HTML. For example, text can be <em>emphasized</em>, made <strong>bold</strong>, and so on. Here is an HTML link to <a href="http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/HeaderDoc/">HeaderDoc documentation</a>. 

This is an unordered list, using HTML tags:
<ul><li>Item 1.</li>
<li>Item 2.</li>
<li>Item 3.</li>
</ul>
		
Numbered lists are recognized automatically (no need for HTML tags):
1. First item.
2. Second item.
3. Third item.

Images can be inserted:

<img src="/Library/WebServer/Documents/PoweredByMacOSX.gif" alt="Logo" />

Tables, too:

<table border="1">
<tr>
<td>row 1, cell 1</td>
<td>row 1, cell 2</td>
</tr>
<tr>
<td>row 2, cell 1</td>
<td>row 2, cell 2</td>
</tr>
</table>
*)
script MyScript
	(*! @function foo
	@abstract A handler.
	@discussion A longer explanation of the handler's purpose.
	The \@function tag above is optional.
	@param x Here goes a description for the first parameter.
	@param y Here goes a description for the second parameter.
	@result The return value.
	*)
	on foo(x, y)
	end foo
	
	(*! @function bar
	@abstract A handler.
	@discussion A longer explanation of the handler's purpose.
	@param z A parameter.
	@result The return value
	@seealso foo foo()
	*)
	on bar(z)
	end bar
end script

To generate and view the documentation, save the above script as MyScript.applescript and run the following in the Terminal:

The tool generates a lot of warnings (errors?) for this simple example. I have no idea if this is expected behaviour, it’s a bug, or my source’s syntax is wrong, but the resulting documentation seems correct. The only warning I have been able to suppress is the one about the missing default encoding. For that, just define the LANG variable before running the tool, like this (using the Bash shell):

Someone trying the above may have been disappointed by the fact that, as of version 8.8.38 (the one shipping with Xcode 4.1 and 4.2), HeaderDoc support for AppleScript is somewhat buggy. Fortunately, a patch has been recently made available, which fixes most of the bugs and makes the tool of practical use (see this thread for the details: http://lists.apple.com/archives/headerdoc-dev/2011/Aug/msg00000.html). Here are step-by-step instructions on how to fix HeaderDoc:

(1) Download the patch and the test suite for HeaderDoc 8.8.38 from http://www.darwin-development.org/headerdoc_patches/Lion/. The needed file are 01_applescript_and_parms.diff and testsuite.tar.gz (ignore 01_testsuite.tar.gz, which is older). The following assumes that the files have been downloaded into ~/Downloads/headerdoc-patches.

(2) Open the Terminal and extract the test suite by issuing the following commands:

(3) Download HeaderDoc source code from http://www.opensource.apple.com/release/mac-os-x-1072/ (the archive is called headerdoc-8.8.38.tar.gz).

(4) Replace the test suite and apply the patch:

(5) Build HeaderDoc. For this, you will need the Perl modules YAML and Data::Plist::XMLWriter, which can be installed with the following commands:

Then, to build HeaderDoc, just type

and go for a coffee. The build process should end with:

You may safely ignore this error.

Great, you’re ready to go! To build the documentation of your script, just run:

Optionally, you may install the patched HeaderDoc system-wide (replacing the version installed by XCode) with:

No more obscure, undocumented, AppleScript code!

My script objects goes through a script and automatically generate an sdef file who reads the source code and comments in the script. This way I can document my scripts with little effort and open with script editor again.

(I guess you’re referring to this post: http://macscripter.net/viewtopic.php?id=35226, which, in fact, I had missed.)

Your approach is very interesting! Seeing the documentation in the editor itself as a dictionary is nice. How about adapting your script to parse (a subset of) HeaderDoc-style comments? One could then output both HTML and sdef from the same source :wink:

You’re right. I’m still using it for myself and I could take a look for headerdocs as well. But like I said in the other topic, it works for me but for publications it needs to be modified and also there need to be interest in the project. At least i found one :smiley:

The following is a simplistic and certainly buggy parser script for HeaderDoc comments (tested only on. itself). No attempt is made to clean up the fields or to apply optimizations. I let you do the rest :wink:

(*!
@header
	HeaderDoc AppleScript Parser
@abstract
	Extracts (a subset of) HeaderDoc comments from an AppleScript source.
@version 0.0.0
@copyright 2011 Use At Your Own Risk :)
*)

(*! @property |TAB|
@abstract The tabulation character. *)
property |TAB| : string id 9
(*! @abstract New line character. *)
property LF : string id 10
property SP : string id 32

set {sourcename, sourcepath} to chooseFile("Please choose an AppleScript source file:", "com.apple.applescript.text", path to scripts folder from user domain)
set source to readFile(sourcepath)
set comments to textBetween(source, "(*!", "*)")
set theCommentDataStructure to {} -- A list of (heterogeneous) records containing the information extracted from the source code
repeat with comment in (a reference to comments)
	-- Parse comment
	if comment contains "@header" then
		set header to getHeaderDocUniqueField(comment, "@header") -- text
		set abstract to getHeaderDocUniqueField(comment, "@abstract") -- text
		set discussion to getHeaderDocUniqueField(comment, "@discussion") -- text
		set theVersion to getHeaderDocUniqueField(comment, "@version") -- text
		set copyright to getHeaderDocUniqueField(comment, "@copyright") -- text
		set the end of (a reference to theCommentDataStructure) to {header:header, abstract:abstract, discussion:discussion, |version|:theVersion, copyright:copyright}
	else
		set function to getHeaderDocUniqueField(comment, "@function") -- text
		set theClass to getHeaderDocUniqueField(comment, "@class") -- text
		set theProperty to getHeaderDocUniqueField(comment, "@property") -- text
		set abstract to getHeaderDocUniqueField(comment, "@abstract") -- text
		set discussion to getHeaderDocUniqueField(comment, "@discussion") -- text
		set params to getHeaderDocField(comment, "@param") -- List of parameters
		set theResult to getHeaderDocUniqueField(comment, "@result") -- text
		set throws to getHeaderDocField(comment, "@throws") -- List of exceptions
		set seealso to getHeaderDocField(comment, "@seealso") -- List of cross-references
		-- The following assumes that the commented element
		-- comes on the line immediately following the comment (no empty lines allowed)
		set commentedElement to textBetween(source, "(*!" & comment & "*)" & LF, LF)
		if function is not missing value then
			set type to "handler"
		else if theClass is not missing value then
			set type to "script"
		else if theProperty is not missing value then
			set type to "property"
		else
			set type to "missing value"
		end if
		-- Parse parameters
		set paramList to {} -- List of records of the form {|name|: <value>, |description|: <value> }
		repeat with param in (a reference to params)
			set x to split(param, {SP, LF, |TAB|})
			set the end of (a reference to paramList) to {|name|:the first item of x, |description|:join(the rest of x, SP)}
		end repeat
		set the end of (a reference to theCommentDataStructure) to {type:type, abstract:abstract, discussion:discussion, params:paramList, |result|:theResult, throws:throws, seealso:seealso, signature:commentedElement}
	end if
end repeat
return theCommentDataStructure

------------------------------------
-- HeaderDoc-specific handlers
------------------------------------
(*!
@abstract Gets the value of the specified HeaderDoc tag.
@discussion This handler parses a HeaderDoc comment and returns a list of values for the specified tag name. A tag is defined as whatever text there is between the tag name and the first occurrence of another HeaderDoc tag or AppleScript's end-of-comment tag (whatever comes first). Note, in particular, that escaped <tt>@</tt> characters (that is, <tt>\@</tt>) will confuse this handler.
@param comment The text of the comment.
@param tag The name of a HeaderDoc tag.
@result A list of values for the given field.
*)
on getHeaderDocField(comment, tag)
	textBetween(comment & "*)", {tag & SP, tag & LF, tag & |TAB|}, {"*)", "@"})
end getHeaderDocField

(*!
@abstract Returns the (unique) value associated to the given tag.
@discussion This handler assumes that there is a unique occurrence of the given tag within the comment.
*)
on getHeaderDocUniqueField(comment, tag)
	try
		the first item of textBetween(comment & "*)", {tag & SP, tag & LF, tag & |TAB|}, {"*)", "@"})
	on error -- Field not present
		missing value
	end try
end getHeaderDocUniqueField

-----------------------------------
-- File manipulation
-----------------------------------

on chooseFile(msg, type, defaultLocation)
	set theAlias to choose file with prompt msg of type {type} ¬
		default location defaultLocation ¬
		without invisibles, multiple selections allowed and showing package contents
	{basename(theAlias), theAlias}
end chooseFile

(*! @function basename
@abstract
	Gets the basename from a path.
@param pathname A (POSIX) path. 
@result
	The base filename of the given path. Example: basename("HD:Users:me:Downloads:filename.txt") is "filename".
*)
on basename(pathname)
	set bn to split(last item of split(POSIX path of pathname, "/"), ".")
	if (count bn) > 1 then return join(items 1 thru -2 of bn, ".")
	return bn as text
end basename


on readFile(thePath)
	read (POSIX file (POSIX path of thePath) as alias) from 1 to eof as text
end readFile

---------------------------
-- Text-related handlers
---------------------------

on split(theText, theDelim)
	set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, theDelim}
	set theResult to the text items of theText
	set AppleScript's text item delimiters to tid
	return theResult
end split

on join(theList, theDelim)
	set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, theDelim}
	set theResult to theList as text
	set AppleScript's text item delimiters to tid
	return theResult
end join

(*!
@abstract
	Finds all substrings enclosed between the given delimiters.
@result
	A list of the occurrences in the given text that are enclosed by the given delimiters. Note that the delimiters <strong>must be distinct</strong>.
*)
on textBetween(theText, leftDelim, rightDelim)
	set t to the rest of split(theText, leftDelim)
	set theResult to {}
	repeat with rec in t
		set s to split(rec, rightDelim)
		if length of s > 1 then set the end of theResult to the first item of s
	end repeat
	theResult
end textBetween