The longer I program, the more structured my programming methods have become. Gone are the days of editing live spaghetti code directly on the server or frantic FTPing files after each tiny change. Today I not only stuff everything in Subversion just to keep track of changes, I also use it as a deployment mechanism. But I want more and I want it automated too! Currently I am busy playing with generated documentation and unit testing. Generated documentation is an all round great idea, but it has a drawback: You need to generate it all the time. So I set out to use Subversion’s post-commit hook to generate fresh documentation for my PHP projects using phpDocumentor.
I have written a little Python script that you can call from Subversion’s post commit hook. This script scans your subversion project for files that have the phpdoc property set. If any of these have changed, then it regenerates your documentation using phpDocumentor. It can also deal with files that are not kept in your Subversion repository and supports anything also supported by phpDocumentor.
Uh, a Python script? For a PHP application?
Well, yes. I have two answers for that, a long one and a short one. The short one is “Meh.” The long one is “It doesn’t matter.” Both Python and PHP are great languages to write little commandline scripts with, each having features the other one doesn’t have. Python’s list comprehensions make for beautiful concise code while PHP’s arrays are just much more flexible. Python’s exception handling is vastly superiour to PHP’s haphazard error handling, but lack of an ordered dictionary in Python make certain applications (such as SOAP support) hard. Okay, enough of my little Python versus PHP rant and back to generation documentation.
Configuring post-commit-phpdoc.py
You can check out the post-commit-phpdoc.py script in my Subversion repository or download it directly. To check out the latest version, use:
- svn checkout svn://svn.jejik.com/phpdoc-tpl/trunk/tools/post-commit-phpdoc.py
Put it somewhere convenient and execute it with the --help flag to see the documentation.
- usage: post-commit-phpdoc.py [options] -r <repos-path> -rev <revision> -c <checkout-path> -s <svn-path> -t <target-path> [Arguments for PHPDoc]
- options:
- -h, --help show this help message and exit
- -r REPOS, --repos=REPOS
- The repository path as passed by Subversion.
- --rev=REVISION The revision as passed by Subversion.
- -c CHECKOUT, --checkout-path=CHECKOUT
- The path where a checkout of the above svn-path is
- kept. E.g:
- "/var/local/apidocs/checkouts/myproject/trunk/"
- -s SVN_PATH, --svn-path=SVN_PATH
- A path in your subversion repository that you want to
- generate documentation for. E.g: "trunk/".
- -t TARGET, --target=TARGET
- The path where you want to put the generated
- documentation. E.g:
- "/var/local/apidocs/templates/myproject/".
- -d, --debug Enable debug output
- --pre-update=PRE_UPDATE
- A file that will be executed before the checkout copy
- is updated.
- --post-update=POST_UPDATE
- A file that will be executed after the checkout copy
- is updated.
- --exclude=EXCLUDE Exclude subversion paths from reading phpdoc
- properties. Excludes are applied before includes.
- --include=INCLUDE Inlcude only the matched sunversion paths when reading
- phpdoc properties. Includes are applied after
- excludes.
Setting up the post-commit hook is quite easy. First, you need to create a directory where you want to store the generated documentation and a checkout of the project. After each commit to your project, the post-commit hook will update the checked out copy and use it to generate documentation. Because everything happens as in the post-commit hook, you should create everything as the user your subversion server runs as, e.g. the svn user or the www-data user.
- sudo mkdir /var/local/apidocs
- sudo chown svn:svn /var/local/apidocs
- mkdir /var/local/apidocs/checkouts
- mkdir /var/local/apidocs/checkouts/yourproject
- mkdir /var/local/apidocs/docs
- mkdir /var/local/apidocs/docs/yourproject
Next, check out a working copy of your project to the checkout directory. You don’t need to checkout the entire repository. Just checking out the portion that you want to generate documentation from is enough.
- cd /var/local/apidocs/checkouts/yourproject
- svn checkout file:///path/to/your/repository/yourproject/trunk
Now you can setup the post-commit hook. Create a new executable file called post-commit in your repository’s hooks directory, or modify your existing post-commit hook. Append the post-commit-phpdoc.py command.
- #!/bin/sh
- /path/to/post-commit-phpdoc.py --repos="$1" --rev="$2" --checkout-path=/var/local/apidocs/checkouts/yourproject/trunk/ \
- --svn-path=trunk/ --target=/var/local/apidocs/docs/yourproject/ -- -o HTML:Smarty:PHP -ti "Your Project Name"
The --repos and --rev options are the variables passed by subversion to the post-commit hook. The --checkout-path path should point to the checkout you made above. The --svn-path should contain the path inside your subversion repository that you checked out. In this example, we checked out the trunk directory. Everything after the -- switch is passed straight to phpDocumentor. Read the phpDocumentor manual to learn about it's options.
There are a few extra arguments that you can pass to post-commit-phpdoc.py that can be useful. With --pre-update and --post-update you can point to an executable that should be executed just before or just after the post-commit hook updates the checked out repository. This is useful for clean-up or GNU make actions for example. With the --include and --exclude options you can filter what files will have their svn properties checked. This can be used to filter out ubversion branches or tags for example. For instance, if you have a subversion repository containing multiple projects, but you want one big documentation for all of them:
- repository
- | project-1
- | | branches
- | | tags
- | | trunk
- | project-2
- | | branches
- | | tags
- | | trunk
You can make a checkout of your entire repository to the checkouts directory, then pass --include=*/trunk/* as an option to only generate documentation for the trunk of each project. Note that --exclude works before --include, so if you exclude a certain portion of your checkout, it will not be checked to see if it contains anything that should be included after all.
Finally, there is also a --debug switch. When you turn this on, all the script’s output is logged to stdout, such as the output from svn update or the phpdoc command.
Tagging your PHP files
Once you have configured post-commit-phpdoc.py you can start tagging your PHP files. post-commit-phpdoc.py looks for files and directories tagged with a phpdoc property. If you tag a file with phpdoc then that file will be included in the list of files that phpDocumenter uses for documentation. The content of the phpdoc property can be empty. It's ignored by the post-commit hook.
If you use the phpdoc property on a directory instead of a file, then you can set the property content to a comma separated list of files and use wildcards as well. This is useful to include generated PHP files or other files not committed to your subversion repository. Take the following subversion tree for example:
- foodir
- | foo.php
- | bar.php.in
- quu.php
- quux.php
- Makefile
In here, the Makefile generates bar.php from bar.php.in. If you wanted to generate documentation from it, check out a copy of the tree from subversion and set the following svn properties for example:
- svn propset "phpdoc" "foo.php,bar.php" foodir
- svn propset "phpdoc" "" quu*.php
After this, commit your working copy and watch the documentation being generated on your subversion server. Simply create an Apache virtual host or a symbolic link from /var/www to the target directory you passed to post-commit-phpdoc.py to view your documentation online. That's all there is too it. Happy coding!
Comments
#1 pcdinh
Are you using PHP4? Since PHP5, a try/catch/throws block can be used for error handling. However every error is an exception is not a fact in PHP. I love that idea. PHP is not Java
#2 Sander Marechal (http://www.jejik.com)
#3 Benjamin W ster
I like the ideas of tagging files to be parsed and only regenerating documentation for files that have been changed.
However, it seems i've found a bug: If files are delted and this change is commited, their documentation won't be removed.
But nevertheless: Thanks for this script and the how-to!
#4 Sander Marechal (http://www.jejik.com)
#5 Ales (http://www.der-stadtplaner.de)
i have a problem with this script. My docs are not generated, debug mode fails with the great note "nothing parsed". The output of your command looks fine, beside the ending of the files, e.g. "..../Class.php\xc2" - the \xc2 looks kinda suspicious. Do you have any tipp for me?
Thanks in advance,
Ales
#6 Sander Marechal (http://www.jejik.com)
If you find it but get stuck, post the output of those commands here (trimmed down please) or on e.g. pastebin.com and let me know.
#7 Ales (http://www.der-stadtplaner.de)
I've tried to execute the script by hand; i don't know why, but a '?' was appended to every filename. When executing the script as a post-commit it works.
Comments have been retired for this article.