<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>David Raynes Technical</title>
    <link rel="alternate" type="text/html" href="http://www.rayners.org/technical/" />
    <link rel="self" type="application/atom+xml" href="http://www.rayners.org/technical/atom.xml" />
   <id>tag:www.rayners.org,2007:/technical//26</id>
    <link rel="service.post" type="application/atom+xml" href="http://mt.rayners.org/mt-atom.cgi/weblog/blog_id=26" title="David Raynes Technical" />
    <updated>2007-05-01T03:43:01Z</updated>
    
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.33</generator>
 
<entry>
    <title>Gotcha in the upgrade_functions plugin argument</title>
    <link rel="alternate" type="text/html" href="http://www.rayners.org/technical/2007/04/gotcha_in_the_upgrade_functions_plugin_argument.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://mt.rayners.org/mt-atom.cgi/weblog/blog_id=26/entry_id=1153" title="Gotcha in the upgrade_functions plugin argument" />
    <id>tag:www.rayners.org,2007:/technical//26.1153</id>
    
    <published>2007-05-01T03:45:20Z</published>
    <updated>2007-05-01T03:43:01Z</updated>
    
    <summary>Plugins these days can, and often do, store their own data in their own custom object classes. The MT::Plugin class makes it very easy to set all that up for each plugin, along with versioning the overall data storage scheme...</summary>
    <author>
        <name>rayners</name>
        <uri>http://www.rayners.org</uri>
    </author>
            <category term="Plugin Development" />
    
    <content type="html" xml:lang="en-us" xml:base="http://www.rayners.org/technical/">
        <![CDATA[<p>Plugins these days can, and often do, store their own data in their own custom object classes.  The <a href="http://code.sixapart.com/docs/movabletype/MT/Plugin.html">MT::Plugin</a> class makes it very easy to set all that up for each plugin, along with versioning the overall data storage scheme for the plugin and allowing for routines to be run upon upgrading.</p>

<p>Here is an example cribbed from the documentation:</p>

<pre><code>schema_version =&gt; 1.1,
object_classes =&gt; [ 'MyPlugin::Foo', 'MyPlugin::Bar' ],
upgrade_functions =&gt; {
    'my_plugin_fix_field_a' =&gt; {
        version_limit =&gt; 1.1,   # runs for schema_version &lt; 1.1
        code =&gt; \&amp;plugin_field_a_fixer
    }
}
</code></pre>

<p>Now, while I will readily admit this was very much a faulty assumption on my part, I thought that the keys of that <code>upgrade_functions</code> hash only needed to be unique within the plugin itself.</p>

<p>I was wrong.  Very wrong.</p>

<p>The keys of that hash need to be unique within the entire MT installation.  That means they cannot be the same as any of the core upgrade functions or any upgrade functions that other plugins might define.  It look me about half an hour to track that one down.</p>

<p>Looking back on it now, it makes sense.  Template tags, junk filters, text filters, and so forth all need to be unique within the system as well, so it being the same for upgrade functions really is not that surprising when you think about it.</p>

<p>So, I am publicly introducing the first of my plugin development best practices: <em>Always prepend the name of the plugin to any key in the <code>upgrade_functions</code> hash argument.</em></p>

<p>Yes, this is pretty much what plugin authors do already with template tags (e.g. MTPluginNameTag), but it can't hurt to have this spelled out explicitly for <code>upgrade_functions</code> as well.</p>
]]>
        

    </content>
</entry>
<entry>
    <title>Getting Lighttpd and RequestTracker working together</title>
    <link rel="alternate" type="text/html" href="http://www.rayners.org/technical/2006/09/getting_lighttpd_and_requesttracker_working_together.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://mt.rayners.org/mt-atom.cgi/weblog/blog_id=26/entry_id=1141" title="Getting Lighttpd and RequestTracker working together" />
    <id>tag:www.rayners.org,2006:/technical//26.1141</id>
    
    <published>2006-09-30T21:41:52Z</published>
    <updated>2006-09-30T22:07:27Z</updated>
    
    <summary>I had been fighting for a while to get my lighttpd server to serve up Request Tracker via FastCGI, but I was always having minor configuration issues with it. I finally dug up a handy recipe for a Mason (which...</summary>
    <author>
        <name>rayners</name>
        <uri>http://www.rayners.org</uri>
    </author>
    
    <content type="html" xml:lang="en-us" xml:base="http://www.rayners.org/technical/">
        <![CDATA[<p>I had been fighting for a while to get my <a href="http://www.lighttpd.net/">lighttpd</a> server to serve up <a href="http://bestpractical.com/rt">Request Tracker</a> via FastCGI, but I was always having minor configuration issues with it.  I finally dug up a handy <a href="http://trac.lighttpd.net/trac/wiki/MasonRecipe">recipe</a> for a Mason (which is what <a href="http://bestpractical.com/rt">Request Tracker</a> uses) FastCGI script for use with <a href="http://www.lighttpd.net/">lighttpd</a>.  After incorporating the changes necessary to deal with <a href="http://www.lighttpd.net/">lighttpd</a>'s quirks, I managed to get things working as well as I would like.  The following is the diff generated from the stock <code>mason_handler.fcgi</code> script that comes with <a href="http://bestpractical.com/rt">Request Tracker</a>:</p>

<pre><code>--- mason_handler.fcgi.orig     2006-09-30 16:45:18.000000000 -0400
+++ mason_handler.fcgi  2006-09-01 18:05:16.000000000 -0400
@@ -65,6 +65,16 @@
     $ENV{'ENV'}    = '' if defined $ENV{'ENV'};
     $ENV{'IFS'}    = '' if defined $ENV{'IFS'};

+    my $uri = $ENV{REQUEST_URI};
+    if ($uri =~ /\?/) {
+      $uri =~ /^(.*?)\?(.*)/;
+      $ENV{PATH_INFO} = $1;
+      $ENV{QUERY_STRING} = $2;
+    } else {
+      $ENV{PATH_INFO} = $uri;
+      $ENV{QUERY_STRING} = "";
+    }
+
     Module::Refresh-&gt;refresh if $RT::DevelMode;
     RT::ConnectToDatabase();
</code></pre>

<p>And the portion of my <a href="http://www.lighttpd.net/">lighttpd</a> configuration file handling <a href="http://bestpractical.com/rt">Request Tracker</a>:</p>

<pre><code>  server.document-root = "/usr/local/share/html"
  fastcgi.map-extensions        = ( ".css" =&gt; ".html", ".js" =&gt; ".html", "/" =&gt; ".html" )
  index-file.names              = ( "index.html" )
  url.access-deny               = ( ".mhtml" )
  fastcgi.server                = ( ".html" =&gt;
                                        ((
                                                "socket"        =&gt; "/tmp/rt-fcgi.socket",
                                                "bin-path"      =&gt; "/usr/local/bin/mason_handler.fcgi",
                                                "check-local"   =&gt; "disable",
                                                "min-procs"     =&gt; 2,
                                                "max-procs"     =&gt; 2
                                        ))
                                )
</code></pre>
]]>
        

    </content>
</entry>
<entry>
    <title>Automated plugin testing</title>
    <link rel="alternate" type="text/html" href="http://www.rayners.org/technical/2006/08/automated_plugin_testing.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://mt.rayners.org/mt-atom.cgi/weblog/blog_id=26/entry_id=1140" title="Automated plugin testing" />
    <id>tag:www.rayners.org,2006:/technical//26.1140</id>
    
    <published>2006-08-20T04:29:12Z</published>
    <updated>2006-08-20T05:42:27Z</updated>
    
    <summary> </summary>
    <author>
        <name>rayners</name>
        <uri>http://www.rayners.org</uri>
    </author>
    
    <content type="html" xml:lang="en-us" xml:base="http://www.rayners.org/technical/">
        <![CDATA[<p>One of programming mantras I try to follow is "Bugs are only found once."  When a bug is discovered, an automated test is written to demonstrate it.  When the test passes, the bug is fixed, and with the test around, subsequent versions of the software will be checked to see if it reappers.  I have been more lax than I like with testing my plugins, but I have started to make things right.</p>
]]>
        <![CDATA[<p>A bug in <a href="http://www.rayners.org/plugins/multiblog/">MultiBlog</a> was recently brought to my attention: the file containing a tag that is not used very often did not even compile.  It was just a little typo, but it was in software that I had <em>shipped</em>.  I really should not have let it out of my subversion repository in that condition.</p>

<p>So, I wrote up a quickie test file and I run it pretty often now.  It has already proved itself useful in development.</p>

<pre><code>use Test::More tests =&gt; 8;

require_ok ( 'multiblog.pl' );
require_ok ( 'MultiBlog::Tags::Categories' );
require_ok ( 'MultiBlog::Tags::Comments' );
require_ok ( 'MultiBlog::Tags::Entries' );
require_ok ( 'MultiBlog::Tags::Include' );
require_ok ( 'MultiBlog::Tags::LocalBlog' );
require_ok ( 'MultiBlog::Tags::MultiBlog' );
require_ok ( 'MultiBlog::Tags::Pings' );
</code></pre>

<p>Using Test::Harness's prove utility (<code>prove -l -I/home/rayners/mt/MT-3.31/lib t/00-require.t</code>), I can make sure that all of the files involved in my plugin compile correctly.</p>

<p>The next step is to start building up some MT testing infrastructure using SQLite and begin testing the actual plugin functionality.</p>
]]>
    </content>
</entry>
<entry>
    <title>Programmatically setting plugin defaults</title>
    <link rel="alternate" type="text/html" href="http://www.rayners.org/technical/2006/05/programmatically_setting_plugin_defaults.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://mt.rayners.org/mt-atom.cgi/weblog/blog_id=26/entry_id=1122" title="Programmatically setting plugin defaults" />
    <id>tag:www.rayners.org,2006:/technical//26.1122</id>
    
    <published>2006-05-14T04:01:27Z</published>
    <updated>2006-05-14T05:07:39Z</updated>
    
    <summary>When developing a plugin for Movable Type 3.2, you can list out all of the configuration variables you plan on tracking on system-wide or per-blog basis, along with explicit, hard coded default values for those variables. As I was working...</summary>
    <author>
        <name>rayners</name>
        <uri>http://www.rayners.org</uri>
    </author>
            <category term="Plugin Development" />
    
    <content type="html" xml:lang="en-us" xml:base="http://www.rayners.org/technical/">
        <![CDATA[<p>When developing a plugin for Movable Type 3.2, you can list out all of the configuration variables you plan on tracking on system-wide or per-blog basis, along with explicit, hard coded default values for those variables.  As I was working on <a href="http://www.rayners.org/plugins/workflow/">Workflow</a> last night, I discovered a way to programmatically set different default values for individual blogs.</p>
]]>
        <![CDATA[<p>When writing a plugin for Movable Type 3.2, the unofficial standard (as far as I have seen at least) is to build a subclass of <code>MT::Plugin</code>.  Then, in a BEGIN block, create a new instance of this plugin class and pass the constructor all of the plugin information, including configuration variables and their defaults.</p>

<p>Here is a shorted example from the 3.2 update of <a href="http://www.rayners.org/plugins/workflow/">Workflow</a> that I am working on currently:</p>

<pre><code>sub BEGIN {
    $plugin = MT::Plugins::Workflow-&gt;new({
        name =&gt; 'Workflow',
        version =&gt; $VERSION,
        ...
        settings =&gt; new MT::PluginSettings([
            [ 'can_publish', { Default =&gt; undef, Scope =&gt; 'blog' } ],
        ]),
    });
    MT-&gt;add_plugin($plugin);
}
</code></pre>

<p>I am currently working on the updated configuration system for <a href="http://www.rayners.org/plugins/workflow/">Workflow</a> and I was struggling with how and where to set the default publishing settings for each individual blog.  I ended up having the defaults generated and <em>saved</em> if there was no pre-existing configuration when the user navigates to the plugin configuration page in the Movable Type administration interface.  That was a less than ideal solution, if you ask me.  First of all, simply <em>viewing</em> the configuration should <em>never</em> alter it.  And beyond that, anybody who is familiar with the plugin configuration page in Movable Type could tell you that simply visiting the plugin configuration page does not mean that the configuration for any given plugin will be altered or even viewed.  So a user could browse to the plugin configuration page, view and/or alter the configuration for a completely different plugin (e.g. SpamLookup) and end up with a different setup for <a href="http://www.rayners.org/plugins/workflow/">Workflow</a>.  That is absolutely the wrong behavior.</p>

<p>Not willing to settle on that, I did some digging into the <code>MT::Plugin</code> class and after searching for the string "default" in the file, I found the answer to my problem: the <code>apply_default_settings</code> subroutine.  This is called by the <code>get_config_obj</code> subroutine, which is called whenever a plugin object accesses its configuration information (unless, of course, the <code>MT::PluginData</code> object is loaded directly).  So, by overriding the <code>apply_default_settings</code> subroutine in the new plugin class, programmatic defaults can be generated.</p>

<p>For example, having the following subroutine in my plugin class for <a href="http://www.rayners.org/plugins/workflow/">Workflow</a> allows me to set the default publishing permissions on a per-blog basis (<code>_default_perms</code> is a method in the plugin class for <a href="http://www.rayners.org/plugins/workflow/">Workflow</a> that generates the default permissions for a given blog):</p>

<pre><code>sub apply_default_settings {
    my $plugin = shift;
    $plugin-&gt;SUPER::apply_default_settings (@_);

    my ($data, $scope_id) = @_;

    if ($scope_id =~ /blog:(\d+)/) {
        my $blog_id = $1;
        if (!defined $data-&gt;{ can_publish }) {
            $data-&gt;{ can_publish } = $plugin-&gt;_default_perms($blog_id);
        }
    }
}
</code></pre>

<p>So, by defining my own <code>apply_default_settings</code> subroutine, I can have a plugin generate defaults in any way that I need.</p>
]]>
    </content>
</entry>
<entry>
    <title>Building Plugin Distributions with Ant</title>
    <link rel="alternate" type="text/html" href="http://www.rayners.org/technical/2006/03/building_plugin_distributions_with_ant_1.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://mt.rayners.org/mt-atom.cgi/weblog/blog_id=26/entry_id=1088" title="Building Plugin Distributions with Ant" />
    <id>tag:www.rayners.org,2006:/technical//26.1088</id>
    
    <published>2006-03-28T03:27:52Z</published>
    <updated>2006-03-28T04:15:58Z</updated>
    
    <summary>I use Ant to build the plugin archives that can be downloaded from my site. Why Ant? Well, while I can manage to get make to compile a couple files, I have more experience with Ant, mostly because that is...</summary>
    <author>
        <name>rayners</name>
        <uri>http://www.rayners.org</uri>
    </author>
            <category term="Automation" />
    
    <content type="html" xml:lang="en-us" xml:base="http://www.rayners.org/technical/">
        <![CDATA[<p>I use <a href="http://ant.apache.org/">Ant</a> to build the plugin archives that can be downloaded from my site.  Why <a href="http://ant.apache.org/">Ant</a>?  Well, while I can manage to get <a href="http://www.gnu.org/software/make/">make</a> to compile a couple files, I have more experience with <a href="http://ant.apache.org/">Ant</a>, mostly because that is what we use at work right now.</p>
]]>
        <![CDATA[<p>I keep the <code>build.xml</code> file in a directory named <code>plugins/</code> off of my home directory on my development machine (a <a href="http://www.freebsd.org/">FreeBSD</a> box for those following along at home).  It will <a href="http://svnbook.red-bean.com/nightly/en/svn.ref.svn.c.export.html">export</a> the tagged release for the given plugin into a <code>plugins/plugin-envelope-name/</code> directory, replace all instances of <code>@VERSION@</code> in the plugin with the version number, move any files in <code>plugins/plugin-envelope-name/php/</code> to <code>php/plugins/</code>, builds the .tar.gz and .zip files for distribution, and scp's them to my server for download,  And to do all that, once I tag a release for a particular plugin, all I have to do is the following:</p>

<pre><code>ant -Dversion=version.number plugin-name
</code></pre>

<p>And just for reference, here is a sampling of the <strong><a href="http://www.rayners.org/plugins/multiblog/">MultiBlog</a></strong> repository to show what this script expects:</p>

<ul>
<li><code>trunk/</code>
<ul>
<li><code>multiblog.pl</code></li>
<li><code>lib/</code></li>
<li><code>php/</code></li>
<li><code>tmpl/</code></li>
</ul></li>
<li><code>tags/</code>
<ul>
<li><code>1.1.1/</code></li>
<li><code>1.99.2b/</code></li>
</ul></li>
</ul>

<p>It may not be perfect, and it is far from complete, but it certainly works well enough for me right now.  In the not too distant future, I plan on adding the actual release tagging procedure and automatic updating of the plugin pages with the new version number.  At some point, I would like to investigate other build tools, but like I said earlier, this works well enough for me for the time being.</p>

<p>Here is the <code>build.xml</code> file:</p>

<pre><code>&lt;?xml version="1.0"?&gt;
&lt;project default="deploy" basedir="."&gt;

  &lt;property name="svn-exec" value="/usr/local/bin/svn" /&gt;
  &lt;property name="svn-proto" value="svn+ssh" /&gt;
  &lt;property name="svn-host" value="rayners.org" /&gt;
  &lt;property name="svn-base" value="/home/rayners/repos/" /&gt;
  &lt;property name="svn-tag-base" value="tags/" /&gt;

  &lt;property name="scp-user" value="rayners" /&gt;
  &lt;property name="scp-host" value="hostname" /&gt;
  &lt;property name="scp-dir" value="/home/rayners/www/plugins/" /&gt;

  &lt;property name="ssh-key" value="/home/rayners/.ssh/id_dsa" /&gt;
  &lt;property name="ssh-key-pass" value="" /&gt;
  &lt;property name="plugin-dir" value="${basedir}/${plugin}" /&gt;

  &lt;property name="remote-move-dest-base" value="/home/rayners/www/plugins/" /&gt;

  &lt;macrodef name="export-plugin"&gt;
    &lt;attribute name="plugin" default="NOT SET" /&gt;
    &lt;attribute name="envelope" default="NOT SET" /&gt;
    &lt;attribute name="version" default="NOT SET" /&gt;
    &lt;sequential&gt;
      &lt;delete dir="${basedir}/@{plugin}" /&gt;
      &lt;mkdir dir="${basedir}/@{plugin}/export/plugins" /&gt;
      &lt;exec dir="${basedir}/@{plugin}/export" executable="${svn-exec}"&gt;
        &lt;arg line="--force export ${svn-proto}://${svn-host}${svn-base}@{plugin}/${svn-tag-base}@{version} plugins/@{envelope}" /&gt;
      &lt;/exec&gt;
      &lt;replace dir="${basedir}/@{plugin}/export/plugins/@{envelope}" token="@VERSION@" value="@{version}"&gt;
        &lt;include name="**/*" /&gt;
      &lt;/replace&gt;
      &lt;move file="${basedir}/@{plugin}/export/plugins/@{envelope}/php" tofile="${basedir}/@{plugin}/export/php/plugins" /&gt;
    &lt;/sequential&gt;
  &lt;/macrodef&gt;

  &lt;macrodef name="build-plugin-archives"&gt;
    &lt;attribute name="plugin" default="NOT SET" /&gt;
    &lt;attribute name="version" default="NOT SET" /&gt;
    &lt;sequential&gt;
      &lt;zip destfile="${basedir}/@{plugin}/@{plugin}-@{version}.zip"
           basedir="${basedir}/@{plugin}/export/"
      /&gt;
      &lt;tar destfile="${basedir}/@{plugin}/@{plugin}-@{version}.tar"
           basedir="${basedir}/@{plugin}/export/"
      /&gt;
      &lt;gzip src="${basedir}/@{plugin}/@{plugin}-@{version}.tar"
            destfile="${basedir}/@{plugin}/@{plugin}-@{version}.tar.gz"
      /&gt;
      &lt;delete file="${basedir}/@{plugin}/@{plugin}-@{version}.tar" /&gt;
    &lt;/sequential&gt;
  &lt;/macrodef&gt;

  &lt;macrodef name="deploy-archives"&gt;
    &lt;attribute name="plugin" default="NOT SET" /&gt;
    &lt;attribute name="version" default="NOT SET" /&gt;
    &lt;sequential&gt;
      &lt;scp todir="${scp-user}@${scp-host}:${scp-dir}@{plugin}" 
           keyfile="${ssh-key}"
           passphrase="${ssh-key-pass}"
           trust="true"&gt;
        &lt;fileset dir="${basedir}/@{plugin}"&gt;
          &lt;include name="@{plugin}-@{version}.*" /&gt;
        &lt;/fileset&gt;
      &lt;/scp&gt;
    &lt;/sequential&gt;
  &lt;/macrodef&gt;

  &lt;macrodef name="deploy-plugin-version"&gt;
    &lt;attribute name="plugin" default="NOT SET" /&gt;
    &lt;attribute name="envelope" default="NOT SET" /&gt;
    &lt;attribute name="version" default="NOT SET" /&gt;
    &lt;sequential&gt;
      &lt;export-plugin plugin="@{plugin}"
                     envelope="@{envelope}"
                     version="@{version}" /&gt;
      &lt;build-plugin-archives plugin="@{plugin}"
                             version="@{version}" /&gt;
      &lt;deploy-archives plugin="@{plugin}"
                       version="@{version}" /&gt;
    &lt;/sequential&gt;
  &lt;/macrodef&gt;

  &lt;target name="multiblog"&gt;
    &lt;deploy-plugin-version plugin="multiblog"
                           envelope="MultiBlog"
                           version="${version}" /&gt;
  &lt;/target&gt;

  &lt;target name="workflow"&gt;
    &lt;deploy-plugin-version plugin="workflow"
                           envelope="Workflow"
                           version="${version}" /&gt;
  &lt;/target&gt;

  &lt;target name="parentcategoryrebuild"&gt;
    &lt;deploy-plugin-version plugin="parentcategoryrebuild"
                           envelope="ParentCategoryRebuild"
                           version="${version}" /&gt;
  &lt;/target&gt;

  &lt;target name="feedburner"&gt;
    &lt;deploy-plugin-version plugin="feedburner"
                           envelope="FeedBurner"
                           version="${version}" /&gt;
  &lt;/target&gt;

  &lt;target name="dropcash"&gt;
    &lt;deploy-plugin-version plugin="dropcash"
                           envelope="Dropcash"
                           version="${version}" /&gt;
  &lt;/target&gt;

&lt;/project&gt;
</code></pre>
]]>
    </content>
</entry>

</feed> 

