PHP Like a Pro: Part 3

Welcome to the third installment of PHP-ing like a productive person. It’s code writing time. Here is how I like to start:

touch index.php

Yes, I’m a toucher. Sue me!

The Front Controller

The index file seems like a good place to start, right? That’s the first place your browser will hit. This is the central point of your application. And just for kicks, I will designate this file to be my front controller. What does that mean? It means that it will do all the routing. It will capture the GET requests from the visitors and then send them where they need to go.

One of the things I want in this pastebin incarnation are nice URL’s. This, is what I consider an ugly URL:

http://example.com/?paste=2345

On the other hand, this is what I consider a nice(er) URL:

http://example.com/2345

What’s the difference here? Well, the difference is in how Apache understand these two requests. The first one means “Yo, Apache, bring me index.php – I have a GET request for him”. The second means “Yo, Apache – I want to go to the 2345 folder and see what’s there!”

In other words, if you use the second URL Apache will try to find an index file in /vagrant/www/2345 directory. We don’t want that. We want our own application handle all the routing in hourse so to speak. How do we accomplish that?

We tell apache to screw off – that’s how. There are two ways to do this. In our vagrant server the easiest way to get it done is to use Apache ModRewrite. First, let’s enable it:

sudo a2enmod rewrite

Next let’s upgrade out site config:

sudo vi /etc/apache2/sites-available/project

In that file we need to add 5 new lines under the Directory heading. Everything under allow from all is new:


    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    allow from all
    
    # enable the rewrite engine
    RewriteEngine On
    RewriteBase /
    
    # ignore file requests like /foo.php
    RewriteCond %{REQUEST_FILENAME} !-f
    
    # ignore directory requests like /foo/
    RewriteCond %{REQUEST_FILENAME} !-d

    # route stuff like /foo.php or /foo to index.php
    RewriteRule . /index.php [L]

Or you can add the same lines in a .htaccess file in your project directory. The above method is considered a bit safer, but you do what you must.

Once you add these lines and restart Apache, all requests will be re-routed to index.php. Right now now that file is blank so there is nothing exciting going on yet. So let’s change that.

showPasteForm();
} elseif($uri == "/paste") {
    $pasteCtrl->addNewPaste();
} elseif(PasteController::isValidPasteURI($uri)) {
    $pasteCtrl->showPasteContents($uri);
} else {
  $pasteCtrl->show404();
}

First line of the code includes Composer’s autoloader. This will allow me to seamlessly use the Twig and RedBean templates without actually having to explicitly include them anywhere. You’ll see this in action in just a minute.

Don’t worry about PasteController.php. It isn’t a thing yet. It does not exist. I just made it up. This code right will bug the fuck out if you try to access it via browser but that’s ok. What I’m doing here is sort of structuring the code in my head and putting it down. Having written all these calls to the non-existent methods gives me a pretty good idea what this new class ought to do:

  • If the uri is / then it will show the blank form
  • If the uri is /paste it will attempt to handle submitted data
  • If it is something else, it will test if the URL is a valid paste number and show associated paste
  • Otherwise, it will display an error

Let’s create the controller then!

Building the first class

I have a feeling you’re going to be sick of me touching things before this is done:

touch PasteController.php

I already know the basic layout of this class because I just drafted it out in my index class. All I have to do is to write it down now:


I added the the "hello world" line so that I can verify my code is working. I can do that by navigating to http://localhost:8080 on my host OS. If I nothing is amiss I should see the age-old one-line greeting echoed back to me.

Creating Twig Templates

Hello world is nice, but I want to make something real. In the first installment of this series I mentioned that echoing HTML from PHP scripts is not the greatest practice. So I want to do this right. I want to set up some Twig templates:

mkdir templates
touch templates/header.html
touch templates/form.html
touch templates/footer.html

Inside header.html I put... Well, header stuff:



    
        {{ title }}
        
    
    

The {{ title }} bit is where we are going to pass the page title. In case you haven't noticed this is the Twig template syntax. The form.html file is equally simple:

{% include 'header.html' %}

    

Paste It


{% include 'footer.html' %}

Yep, I'm importing the header and footer files. Twig will assemble all of it for me into one nice file and I haven't used a single line of PHP in these templates. This is rather elegant, don't you think?

So, now lets twig it up in our controller:

    // this is my twig instance
    private $twig;

    public function __construct()
    {
        // create twig environment and tell it where the templates live
        $loader = new Twig_Loader_Filesystem('templates');

        // set up a twig instance, enable auto-escaping
        $this->twig = new Twig_Environment($loader, array('debug' => true, 
                                                            'autoescape' => true, 
                                                            'auto_reload' => true));
    }

    public function showPasteForm() 
    {
        // load the form template
        $template = $this->twig->loadTemplate('form.html');

        // render and pass in the title at the same time
        echo $template->render(array('title' => 'Paste It'));
    }

Note, no include statements but Twig works! That's the magic of auto loading.

This is more or less all there is to Twig. It takes two lines to set it up, and another line or two to actually use it. There is really no reason not to include this library in your projects.

Testing

Seeing how we have our first class taking shape, it is probably a good idea to start building our first unit test:

mkdir tests
touch tests/PasteControllerTest.php

To make a test we extend the PHPUnit_Framework_TestCase class like this:

ctrl = new PasteController();
    }

    public function testIsValidPasteURI()
    {
        $temp = $this->ctrl->isValidPasteURI("/234");
        $this->AssertTrue($temp);
        
        $temp = $this->ctrl->isValidPasteURI("/foo");
        $this->AssertFalse($temp);

        $temp = $this->ctrl->isValidPasteURI("/foo/434");
        $this->AssertFalse($temp);

        $temp = $this->ctrl->isValidPasteURI("234");
        $this->AssertFalse($temp);

    }
}

We can run it by issuing the command:

phpunit UnitTest tests/PasteControllerTest.php

As you can see I'm testing the "easily testable" method isValidPasteURI. Why? Because it actually returns a value. These sort of methods are easy to unit test because you can just call them repeatedly with a wide variety of test values, and then make assertions about the results. Methods which produce side-effects and return no values (like showPasteForm) are little less testable.

The results should look a bit like this:

Failed Unit Test

Failed Unit Test

Why did this test fail? Well, we made our isValidPasteURI method always return true... My test expects it to return false with some values, and true with others. This was exactly what we should have expected. Now I can go back and fix the method so that it passes all my tests.

We know that a REQUEST_URI will need to start with a / and be followed by a number so here is a possible implementation:

     /**
     * Returns true if $uri contains a valid (numeric) paste id.
     *
     * A valud URI should looke like /234 - anything else is not valid
     *
     * @param string $uri a REQUEST_URI value that should be numeric
     * @return boolean tru if URI is valud, false if it is not
     */
    public static function isValidPasteURI($uri)
    {
        // if starts with / 
        if(strpos($uri, "/") === 0)
        {
            $tmp = substr($uri, 1);

            if(is_numeric($tmp))
                return true;
        }

        return false;
    }

Is this correct? I don't know, let's see:

Passed Unit Test

Passed Unit Test

As far as my Unit Test is concerned, the method performs acceptably. Is my Unit Test correct? Is it exhaustive enough? Probably not, but I can add to it later if I feel extra vigilant.

How do we test methods in which the main functionality is producing side effects? Good question. It seems that we should at least make an effort to test our showPasteForm which renders a Twig template and sends it to the browser. How do we make assertions about output though?

Well, if you just call the method in your test and run phpunit with --verbose --debug attributes, it will dump the raw HTML to the console and you can eyeball it. But that's not really testing - that's checking. We want to test! Sadly, checking output is not really what Unit Testing was designed for.

To test this function we would ideally want to emulate what the browser is doing - so generate a fake request to index.php and see if a form is displayed by patterm matching the HTML. That's not unit testing though. That's acceptance testing, and there are actually frameworks that let us do that - like Codeception for example. But I want to concentrate on unit tests now, which test elements of your code in isolation.

Fortunately PHPUnit has some functionality that can help us test this method. There is a near little assertion named expectOutputRegex. As you can probably imagine it will do some pattern matching on the output. This is how you use it:

    public function testShowPasteForm()
    {
        $this->expectOutputRegex("/Paste It<\/title>/");
        $this->ctrl->showPasteForm();
    }</pre>
<p>This is little backwards from all other tests because you assert firsts and run code afterwards, but it works. The downside of this is that you can usually only make one assertion per test. If you assert multiple things, only the last one will actually be tested. So you better pick a good regex.</p>
<p>What I want to know when this test runs is that Twig assembled my templates correctly. If you remember, in the header template the title was not defined. It contained a variable. In addition the form template did not have a title tag - it imported it. So the only way for the rendered page to have a title, and a correct one is for Twig to have worked correctly. Hence, that's what I'm pattern matching in this test.</p>
<h3>Error Handling</h3>
<p>Let's handle that pesky error situation when someone inadvertently tries to access a URI that is not a valid paste address. For example navigating to <samp>/foo</samp> should produce a 404 error code so that browsers know that nothing is there. We already have a method for that in our PasteController but... Well, is that a good place for it? Why a controller related to paste stuff should handle routing errors?</p>
<p> How about we make an <samp>ErrorController.php</samp> class and encapsulate that functionality away:</p>
<pre lang="php"><?php

class ErrorController
{
    public function show404()
    {
        header('HTTP/1.0 404 Not Found');
    }
}</pre>
<p>This works, but it doesn't really display any meaningful message to the user. The browser knows there was a 404 error, but the user sees a blank page. It would be better if we could pull in our templating engine and display a nicely formatted error message.</p>
<p>But... Well, this is not really an issue but I already initialized Twig and set it up to my liking in PasteController. Now I will have to do it again. And if I define another controller, I will have to do it there too. I know it's only two lines but that's not the case. I'm worried about the configuration options (like enabling the auto-escaping feature). I want it to be consistent throughout my application.</p>
<p>So let me make a <samp>TwigFactory</samp>! It will be a static class that will give you and instance of Twig environment pre-configured to your liking. Both Paste and Error classes will be able to grab it whenever they need it:</p>
<pre lang="php"><?php

class TwigFactory
{
    public static function getTwig()
    {
        $loader = new Twig_Loader_Filesystem('templates');
        return new Twig_Environment($loader, array('debug' => true, 
                                                    'autoescape' => true, 
                                                    'auto_reload' => true));
    }
}</pre>
<h3>Refactoring</h3>
<p>My project directory is getting messy. I have controller classes floating around, I have helper classes (like the TwigFactory) and all kinds of other stuff in there. I think it's time to do some cleanup. It is usually a good idea to commit your existing code right before you're going to make major changes:</p>
<pre lang="bash">git add .
git commit</pre>
<p>This way if we royally mess something up, we can quickly roll back to the last known good state for the project and start over. Now we can proceed with potentially destructive refactoring actions:</p>
<pre lang="bash">mkdir controllers
mkdir helpers
mv *Controller.php controllers/
mv *Helper helpers/</pre>
<p>This should actually make our working environment much cleaner. Observe:</p>
<div id="attachment_13377" style="width: 374px" class="wp-caption aligncenter"><a href="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/directory.png"><img src="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/directory.png" alt="Project Directory Structure" width="364" height="362" class="size-full wp-image-13377" srcset="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/directory.png 364w, http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/directory-150x150.png 150w" sizes="(max-width: 364px) 100vw, 364px" /></a><p class="wp-caption-text">Project Directory Structure</p></div>
<p>Now that I cleaned everything up I will need to go back and correct the include statements everywhere and then re-test everything. Which reminds me, we should build a unit test for Error Controller as well. Good news is that we can crib most of it from the Paste Controller test:</p>
<pre lang="php"><?php
require_once "vendor/autoload.php";
require_once "controllers/ErrorController.php";
require_once "helpers/TwigFactory.php";

class ErrorControllerTest extends PHPUnit_Framework_TestCase
{
    protected $err;

    protected function setUp()
    {
        $this->err = new ErrorController();
    }

    public function testObjectInitialization()
    {
        $this->assertObjectHasAttribute("twig", $this->err);
    }

    public function testShowPasteForm()
    {
        $this->expectOutputRegex("/<h1>404<\/h1>/");
        $this->err->show404();
    }
}</pre>
<p>Now that I have multiple tests I can actually run them together by simply pointing PHPUnit at my test directory like this:</p>
<div id="attachment_13380" style="width: 557px" class="wp-caption aligncenter"><a href="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/tests.png"><img src="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/tests.png" alt="Running Multiple Tests" width="547" height="293" class="size-full wp-image-13380" /></a><p class="wp-caption-text">Running Multiple Tests</p></div>
<h3>Auto Loading and Code Standards</h3>
<p>I just realized something... I've been doing this wrong. I'm kinda blogging along as I assemble this code, and it suddenly struck me that I am not using one of the great features that comes with Composer - auto-loading. Or rather, I'm using it for all the third party classes, but I still have to "require" my own classes. This is silly.</p>
<p>But alas, to actually leverage this feature my code should conform to PSR-0... Which it does not. It is not properly namespaced, and not organized the way it should be. So we need to do even more refactoring. Best do it now, while the code-base is still small and uncomplicated.</p>
<p>The way Composer's autoloading works is that you can specify your own namespace in the <samp>composer.json</samp> like this:</p>
<pre lang="javascript">
    "autoload": {
        "psr-0": {
            "SillyPastebin":       "src/"
        } 
   }</pre>
<p>Here I'm telling composer to look for <samp>SillyPastebin</samp> code in the <samp>src/</samp> directory. This directory does not exist yet. I will have to create it.</p>
<p>Once I have this directory in place, the rest is just a pattern matching game. When you instantiate a namespaced class like this:</p>
<p><samp>SillyPastebin\Namespace\SomeClass</samp> </p>
<p>the composer autoloader simply converts all the php namespace delimiters (these things: <samp>\</samp>) into system path delimiters (in our case <samp>/</samp>) using the directory you specified in the config file as root. So if it sees the invocation I shown above it will attempt to auto-load a class located at:</p>
<p><samp>/src/SillyPastebin/Namespace/SomeClass.php</samp></p>
<p>This is not how my code is organized. I have controller and helper folders in my top level directory and nothing is namespaced. With the code as it is, there is simply no way for me to leverage this great feature. Which means we have some housekeeping and refactoring to do. </p>
<p>First let's move the files around to conform to these conventions:</p>
<pre lang="bash">mkdir src
mkdir src/SillyPastebin
mv controllers/ src/SillyPastebin/Controller
mv helpers/ src/SillyPastebin/Helper</pre>
<p>When finished, my directory structure will look like this:</p>
<div id="attachment_13382" style="width: 452px" class="wp-caption aligncenter"><a href="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/directory2.png"><img src="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/directory2.png" alt="Organizing for PSR-0" width="442" height="336" class="size-full wp-image-13382" /></a><p class="wp-caption-text">Organizing for PSR-0</p></div>
<p>Next let's refactor our code:</p>
<pre lang="php"><?php namespace SillyPastebin\Controller;

class PasteController
{
    private $twig;

    public function __construct()
    {
        $this->twig = \SillyPastebin\Helper\TwigFactory::getTwig();
    }</pre>
<p>And:</p>
<pre lang="php"><?php namespace SillyPastebin\Controller;

class ErrorController
{
    private $twig;

    public function __construct()
    {
        $this->twig = \SillyPastebin\Helper\TwigFactory::getTwig();
    }</pre>
<p>And:</p>
<pre lang="php"><?php namespace SillyPastebin\Helper;

class TwigFactory
{
    public static function getTwig()
    {
        $loader = new \Twig_Loader_Filesystem('templates');
        return new \Twig_Environment($loader, array('debug' => true, 
                                                     'autoescape' => true, 
                                                      'auto_reload' => true));
    }
}</pre>
<p>Note that I had to prefix the Twig classes with <samp>\</samp> to indicate they are not part of the <samp>SillyPastebin\Helper</sam> namespace.</p>
<p>Finally here is how you change the index:</p>
<pre lang="php"><?php require "vendor/autoload.php";

$uri = $_SERVER['REQUEST_URI'];

$pasteCtrl = new SillyPastebin\Controller\PasteController();
$errorCtrl = new SillyPastebin\Controller\ErrorController();</pre>
<p>We'll also need to refactor our tests the same way, but I'm not gonna show that because it's basically more of the same. You remove the require statements, and you add <samp>SillyPastebin\Namespace</samp> in front of all the class instatioation calls and that's about it.</p>
<p>Last thing to do is to update our composer config to make sure the auto-loading feature works:</p>
<pre lang="bash">composer update</pre>
<p>The whole thing took about five minutes, and I achieved something really cool - I will never, ever have to write another require or include statement for this project. The first line of <samp>index.php</samp> is actually the only include that I will ever need from now on. This is very, very cool and I get this for free with Composer as long as I namespace my code the right way... Which is what I should be doing anyway.</p>
<h3>Lessons Learned</h3>
<p>I started this series with the best intentions - I wanted to write this thing right. I wanted to practice what I preached in Part 1, and yet when I sat down and started writing code bad habits started sneaking in. This happens all the time - you forget, or you willingly ignore some rule or best practice. At first it works out fine, but then as you continue working things start to crumble. The code starts looking subtly wrong.</p>
<p>You know what reeled me back onto the correct path again? Unit tests. No seriously, scroll back a few paragraphs and look at the code I posted for <samp>ErrorControllerTest</samp> class. I saw these lines piling up on top:</p>
<pre lang="php">require_once "controllers/ErrorController.php";
require_once "helpers/TwigFactory.php";</pre>
<p>Here is what I realized: I may have more helper classes in the future. Also Models, which we didn't even talk about yet. I will have to include every single one of them, on every single unit test. That's silly. If I had to do this once in my index file, I'd probably be fine with it - but now I saw dozens of unit tests, with long, long lines of includes on top, and it looked wrong.</p>
<p>And so, I ended up refactoring my code to conform to the PSR-0 standard, and to use proper name spacing. Unit testing saved the day once again, and not by detecting errors, but by forcing me to think about how I structure my code. I mentioned this in part one - that's the hidden value of Unit Tests. They make you think not only about what your code is doing, but also how it is doing it.</p>
<p>This is the primary reason why I didn't go back and rework this post to look as if I meant to do it this way all along. It would probably be clearer, but I wanted to showcase how even if you have best intentions you can get easily lulled into complacency and how following some best practices and using the right tools can jolt you right back onto the correct path.</p>
<p>Next time I'll try to finally get some database interaction accomplished using RedBean. Unless I get side-tracked again.</p>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">Related Posts</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-13305" data-post-type="none" ><a href="http://www.terminally-incoherent.com/blog/2012/12/19/php-like-a-pro-part-2/" class="wp_rp_thumbnail"><img src="http://www.terminally-incoherent.com/blog/wp-content/plugins/related-posts_/static/thumbs/22.jpg" alt="PHP Like a Pro: Part 2" width="150" height="150" /></a><a href="http://www.terminally-incoherent.com/blog/2012/12/19/php-like-a-pro-part-2/" class="wp_rp_title">PHP Like a Pro: Part 2</a></li><li data-position="1" data-poid="in-13573" data-post-type="none" ><a href="http://www.terminally-incoherent.com/blog/2013/01/09/php-like-a-pro-part-6-a-new-project/" class="wp_rp_thumbnail"><img src="http://www.terminally-incoherent.com/blog/wp-content/uploads/2013/01/user-new-issue-150x150.png" alt="PHP Like a Pro: Part 6 (A New Project)" width="150" height="150" /></a><a href="http://www.terminally-incoherent.com/blog/2013/01/09/php-like-a-pro-part-6-a-new-project/" class="wp_rp_title">PHP Like a Pro: Part 6 (A New Project)</a></li><li data-position="2" data-poid="in-13267" data-post-type="none" ><a href="http://www.terminally-incoherent.com/blog/2012/12/17/php-like-a-pro-part-1/" class="wp_rp_thumbnail"><img src="http://www.terminally-incoherent.com/blog/wp-content/plugins/related-posts_/static/thumbs/25.jpg" alt="PHP Like a Pro: Part 1" width="150" height="150" /></a><a href="http://www.terminally-incoherent.com/blog/2012/12/17/php-like-a-pro-part-1/" class="wp_rp_title">PHP Like a Pro: Part 1</a></li><li data-position="3" data-poid="in-13388" data-post-type="none" ><a href="http://www.terminally-incoherent.com/blog/2013/01/02/php-like-a-pro-part-4/" class="wp_rp_thumbnail"><img src="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/redbeantest-150x150.png" alt="PHP Like a Pro: Part 4" width="150" height="150" /></a><a href="http://www.terminally-incoherent.com/blog/2013/01/02/php-like-a-pro-part-4/" class="wp_rp_title">PHP Like a Pro: Part 4</a></li><li data-position="4" data-poid="in-13628" data-post-type="none" ><a href="http://www.terminally-incoherent.com/blog/2013/01/18/php-like-a-pro-part-7-first-steps-with-silex/" class="wp_rp_thumbnail"><img src="http://www.terminally-incoherent.com/blog/wp-content/uploads/2013/01/project-dir-150x150.png" alt="PHP Like a Pro: Part 7 (First Steps with Silex)" width="150" height="150" /></a><a href="http://www.terminally-incoherent.com/blog/2013/01/18/php-like-a-pro-part-7-first-steps-with-silex/" class="wp_rp_title">PHP Like a Pro: Part 7 (First Steps with Silex)</a></li><li data-position="5" data-poid="in-13454" data-post-type="none" ><a href="http://www.terminally-incoherent.com/blog/2013/01/04/php-like-a-pro-part-5/" class="wp_rp_thumbnail"><img src="http://www.terminally-incoherent.com/blog/wp-content/uploads/2012/12/codcept-tree-150x150.png" alt="PHP Like a Pro: Part 5" width="150" height="150" /></a><a href="http://www.terminally-incoherent.com/blog/2013/01/04/php-like-a-pro-part-5/" class="wp_rp_title">PHP Like a Pro: Part 5</a></li></ul></div></div>
											</div><!-- .entry-content -->


					<div class="entry-utility">
						This entry was posted in <i class="fa fa-thumb-tack"></i> <a href="http://www.terminally-incoherent.com/blog/category/technology/programming/" rel="category tag">programming</a> and tagged <i class="fa fa-tag"></i> <a href="http://www.terminally-incoherent.com/blog/tag/php/" rel="tag">php</a>, <a href="http://www.terminally-incoherent.com/blog/tag/php-like-a-pro/" rel="tag">php like a pro</a>. Bookmark the <a href="http://www.terminally-incoherent.com/blog/2012/12/26/php-like-a-pro-part-3/" title="Permalink to PHP Like a Pro: Part 3" rel="bookmark">permalink</a>.											</div><!-- .entry-utility -->
				</div><!-- #post-## -->

				<div id="nav-below" class="navigation">
					<div class="nav-previous"><a href="http://www.terminally-incoherent.com/blog/2012/12/24/merry-x-mas-2012/" rel="prev"><span class="meta-nav"><i class="fa fa-arrow-left"></i></span> Merry X-Mas 2012!</a></div>
					<div class="nav-next"><a href="http://www.terminally-incoherent.com/blog/2012/12/28/like-lizard-on-ice-dexter-retrospective/" rel="next">Like Lizard on Ice: Dexter Retrospective <span class="meta-nav"><i class="fa fa-arrow-right"></i></span></a></div>
				</div><!-- #nav-below -->

				<br>

				<!-- Added 04/27/09 Adsense + Related Posts -->

				<br>

				<!-- 
				<p><strong>Related Posts:</strong><br>
				-->
				
				</p>
				<br style="clear:both;">
				<!-- END Adsense + Related Posts -->


				
			<div id="comments">


			<h3 id="comments-title">2 Responses to <em>PHP Like a Pro: Part 3</em></h3>


			<ol class="commentlist">
					<li class="comment even thread-even depth-1" id="li-comment-25150">
		<div id="comment-25150">
		<div class="comment-author vcard">
			<img alt='' src='http://1.gravatar.com/avatar/712051588b3135c9b3b3cbc983e172e1?s=40&d=wavatar&r=r' srcset='http://1.gravatar.com/avatar/712051588b3135c9b3b3cbc983e172e1?s=80&d=wavatar&r=r 2x' class='avatar avatar-40 photo' height='40' width='40' />			<cite class="fn"><a href='http://nullprogram.com/' rel='external nofollow' class='url'>Chris Wellons</a> <img src="http://www.terminally-incoherent.com/blog/wp-content/plugins/comment-info-detector/flags/us.png" title="UNITED STATES" alt="UNITED STATES" class="country-flag" /> <img src='http://www.terminally-incoherent.com/blog/wp-content/plugins/comment-info-detector/browsers/firefox.png' title='Mozilla Firefox 10.0.11' alt='Mozilla Firefox' width='14px' height='14px' class='browser-icon' />   <img src='http://www.terminally-incoherent.com/blog/wp-content/plugins/comment-info-detector/browsers/linux.png' title='Linux ' alt='Linux' width='14px' height='14px' class='os-icon' />   <img src='/img/banana.gif' alt='Terminalist' title='Terminalist' width='16' height='16'></cite> <span class="says">says:</span>		</div><!-- .comment-author .vcard -->
		
		<div class="comment-meta commentmetadata"><a href="http://www.terminally-incoherent.com/blog/2012/12/26/php-like-a-pro-part-3/#comment-25150">
			December 26, 2012 at 8:47 pm</a>		</div><!-- .comment-meta .commentmetadata -->

		<div class="comment-body"><span id="co_25150"><p>I’ve still been running along in parallel with my <a href="https://github.com/skeeto/emacs-pastebin" rel="nofollow">own implementation in Elisp</a>. It’s been a lot of fun! It’s got syntax highlighting, diffs, and supports three different backend databases. Again, here’s a demo hosted for the short-term, by same text editor instance I used to write it. For visitors from the future, here’s <a href="http://i.imgur.com/MWpvT.png" rel="nofollow">a screenshot</a> (and those fuzzy timestamps update live!). Try clicking the “diff” link.</p>
<p><a href="http://zeus.nullprogram.com/pastebin/vQ5Y" rel="nofollow">http://zeus.nullprogram.com/pastebin/vQ5Y</a></p>
<p>I kept the server side as simple as possible. Counting only one backend database, it’s about 150 lines of code and only serves one static page, a few scripts, and a single JSON form. All the heavy work is done client-side, including page generation, syntax highlighting, and unified diffs. The downside is that I bet it’s not very search-engine friendly. I <i>do</i> have some unit tests in place, since you’re using unit tests for your pastebin.</p>
<p>Thanks, this has been very educational.</p>
</span><div class="comment-toolbar" style="text-align: right"><a href="#comment" onclick="CF_Reply('25150','Chris Wellons'); return false;">Reply</a>  |  <a href="#comment" onclick="CF_Quote('25150','Chris Wellons'); return false;">Quote</a></div></div>

		<div class="reply">
					</div><!-- .reply -->
	</div><!-- #comment-##  -->

	</li><!-- #comment-## -->
	<li class="post pingback">
		<p>Pingback: <a href='http://www.terminally-incoherent.com/blog/2015/02/24/unit-testing-sinatra-apps/' rel='external nofollow' class='url'>Unit Testing Sinatra Apps | Terminally Incoherent</a> <img src="http://www.terminally-incoherent.com/blog/wp-content/plugins/comment-info-detector/flags/us.png" title="UNITED STATES" alt="UNITED STATES" class="country-flag" /> <img src='http://www.terminally-incoherent.com/blog/wp-content/plugins/comment-info-detector/browsers/wp.png' title='WordPress 4.1.1' alt='WordPress' width='14px' height='14px' class='browser-icon' />   </p>
	</li><!-- #comment-## -->
			</ol>



	<div id="respond" class="comment-respond">
		<h3 id="reply-title" class="comment-reply-title">Leave a Reply <small><a rel="nofollow" id="cancel-comment-reply-link" href="/blog/2012/12/26/php-like-a-pro-part-3/#respond" style="display:none;">Cancel reply</a></small></h3>			<form action="http://www.terminally-incoherent.com/blog/2012/12/26/php-like-a-pro-part-3/#comment-302000" method="post" id="commentform" class="comment-form">
				<p class="comment-notes"><span id="email-notes">Your email address will not be published.</span> Required fields are marked <span class="required">*</span></p><p class="comment-form-comment"><label for="comment">Comment</label> <script type="text/javascript">var edInserted; if (!edInserted) {edToolbar(); edInserted = true;}</script>
<textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" aria-required="true" required="required"></textarea>
<script type="text/javascript">var edCanvas = document.getElementById('comment');</script></p><p class="comment-form-author"><label for="author">Name <span class="required">*</span></label> <input id="author" name="author" type="text" value="" size="30" maxlength="245" aria-required='true' required='required' /></p>
<p class="comment-form-email"><label for="email">Email <span class="required">*</span></label> <input id="email" name="email" type="text" value="" size="30" maxlength="100" aria-describedby="email-notes" aria-required='true' required='required' /></p>
<p class="comment-form-url"><label for="url">Website</label> <input id="url" name="url" type="text" value="" size="30" maxlength="200" /></p>
<p class="form-submit"><input id="preview" type="submit" name="preview" tabindex="5" value="Preview" /><input id="submit" type="submit" name="submit" tabindex="6" style="font-weight: bold" value="Post" /> <input type='hidden' name='comment_post_ID' value='13343' id='comment_post_ID' />
<input type='hidden' name='comment_parent' id='comment_parent' value='0' />
</p>	<script type='text/javascript'>
	<!--
	refJS = escape( document[ 'referrer' ] );
	document.write("<input type='hidden' name='refJS' value='"+refJS+"'>");
	// -->
	</script>
	<p style="display: none;"><input type="hidden" id="akismet_comment_nonce" name="akismet_comment_nonce" value="323affd073" /></p><noscript><p><strong>Currently you have JavaScript disabled. In order to post comments, please make sure JavaScript and Cookies are enabled, and reload the page.</strong> <a href="https://support.google.com/answer/23852" rel="nofollow external" >Click here for instructions</a> on how to enable JavaScript in your browser.</p></noscript>
<p style="display: none;"><input type="hidden" id="ak_js" name="ak_js" value="103"/></p>			</form>
			</div><!-- #respond -->
	
</div><!-- #comments -->




			</div><!-- #content -->


		</div><!-- #container -->


<div id="ti-nav">

		<div id="primary" class="widget-area" role="complementary">
			<ul class="xoxo">


			<!-- CUSTOM GOOGLE SEARCH -->

			<div>
			<script>
			  (function() {
			    var cx = '002424939659750048775:bjnm3lv2l14';
			    var gcse = document.createElement('script');
			    gcse.type = 'text/javascript';
			    gcse.async = true;
			    gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
			        '//cse.google.com/cse.js?cx=' + cx;
			    var s = document.getElementsByTagName('script')[0];
			    s.parentNode.insertBefore(gcse, s);
			  })();
			</script>
			<gcse:searchbox-only></gcse:searchbox-only>
			</div>

			<!-- END OF CUSTOM GOOGLE SEARCH -->

			<!-- CUSTOM TERMINALLY INCOHERENT LINKS -->
			<li id="myrsslink" class="widget-container" ><h3 class="widget-title">Subscribe</h3>
	                 <ul>
			    <li><a href="http://feeds.feedburner.com/TerminallyIncoherent">	<i class="fa fa-rss-square"></i>	RSS Feed</a></li>
	                </ul>             

			<li id="mytumblrlink" class="widget-container" ><h3 class="widget-title">Social</h3>
	                 <ul>
	                 	
	                 	<li><a href="http://twitter.com/LukeMaciak">			<i class="fa fa-twitter"></i>		Twitter</a></li>
	                 	<li><a href="http://plus.google.com/+LukeMaciak">		<i class="fa fa-google-plus"></i> 	Google+</a></li>
	                 	<li><a href="http://instagram.com/tuxmentat">			<i class="fa fa-instagram"></i> 	Instagram</a></li>
	                 	<li><a href="http://www.pinterest.com/tuxmentat/">		<i class="fa fa-pinterest"></i>		Pinterest</a></li>
	                 	<li><a href="http://devrandom.click">		<i class="fa fa-tumblr-square"></i> 	Tumblr</a></li>
	                 	<li><a href="http://facebook.com/terminally-incoherent.blog">	<i class="fa fa-facebook-square"></i> 	Facebook</a></li>
	                 	<li><a href="http://steamcommunity.com/id/reset_by_peer">	<i class="fa fa-steam"></i> 		Steam</a></li>
				<li><a href="http://reddit.com/u/reset_by_peer">		<i class="fa fa-reddit"></i> 		Reddit</a></li>
	                 	<li><a href="https://medium.com/@LukeMaciak">			<i class="fa fa-maxcdn"></i> 		Medium</a></li>


	                </ul>
	               
			<!-- END OF CUSTOM TERMINALLY INCOHERENT LINKS -->
			

			

					</ul>
		</div><!-- #primary .widget-area -->


		<div id="secondary" class="widget-area" role="complementary">
			<ul class="xoxo">
				<li id="categories-4" class="widget-container widget_categories"><h3 class="widget-title">Categories</h3>		<ul>
	<li class="cat-item cat-item-5"><a href="http://www.terminally-incoherent.com/blog/category/entertainment/" title="This is a top level, catch all category. You probably will be more interested in something more specific like Movies, TV shows, Literature, Comics, Video Games, or RPG Games.">entertainment</a> (676)
<ul class='children'>
	<li class="cat-item cat-item-22"><a href="http://www.terminally-incoherent.com/blog/category/entertainment/comic/" title="Graphic Novel and Manga reviews. Comic book guy style rants, etc...">comics</a> (11)
</li>
	<li class="cat-item cat-item-80"><a href="http://www.terminally-incoherent.com/blog/category/entertainment/literature/" title="Book reviews and literary discussion.">literature</a> (82)
</li>
	<li class="cat-item cat-item-74"><a href="http://www.terminally-incoherent.com/blog/category/entertainment/movies/" title="Movie reviews and discussion.">movies</a> (117)
</li>
	<li class="cat-item cat-item-34"><a href="http://www.terminally-incoherent.com/blog/category/entertainment/rpg/" title="This is the place where I discuss real pen and paper role playing games, tabletop games such as Warhammer, collectible card games and other geeky hobbies. But not LARP-ing because I do not fucking LARP.">rpg and tabletop</a> (62)
</li>
	<li class="cat-item cat-item-77"><a href="http://www.terminally-incoherent.com/blog/category/entertainment/tv/" title="Discussions about TV series, reviews and rants about network decay.">tv</a> (66)
</li>
	<li class="cat-item cat-item-35"><a href="http://www.terminally-incoherent.com/blog/category/entertainment/games/" title="Video game reviews, rants about DRM, derogatory comments about console gaming, and the sad state of gaming industry.">video games</a> (293)
</li>
</ul>
</li>
	<li class="cat-item cat-item-4"><a href="http://www.terminally-incoherent.com/blog/category/futuristic-musings/" title="Random musings on the nature of the future, technological singularity, transhumanism and similar topics. This is where I contemplate the directions in which we are taking our civilization, and where I point out instances where yesterday's science fiction becomes modern day reality.">futuristic musings</a> (61)
</li>
	<li class="cat-item cat-item-15"><a href="http://www.terminally-incoherent.com/blog/category/humor/" title="Memes, old web comic re-posts, funny stories and humorous rants about stuff I find particularly amusing and/or annoying.">geek humor</a> (171)
</li>
	<li class="cat-item cat-item-58"><a href="http://www.terminally-incoherent.com/blog/category/meta/" title="Meta information, personal posts, blog related news and etc.. This is where I apologize when the blog goes down, or warn you if I plan to be MIA for a while.">meta</a> (102)
</li>
	<li class="cat-item cat-item-41"><a href="http://www.terminally-incoherent.com/blog/category/news/" title="This is where I post the infamous 4th of July jokes, and other topical humor.">news & current events</a> (102)
</li>
	<li class="cat-item cat-item-37"><a href="http://www.terminally-incoherent.com/blog/category/random/" title="Posts that don't seem to fit anywhere else.">random stuff</a> (40)
</li>
	<li class="cat-item cat-item-18"><a href="http://www.terminally-incoherent.com/blog/category/school/" title="This category is a bit of a mixed bag. When I was in grad school I used to blog about my daily woes as a student. A lot of the posts dealt with my graduate assistantship duties teaching one class per semester. One I graduated, I kept that job and I still teach that class as an adjunct professor. These days I use this category to write about various aspects of teaching, and my reflections about the state of technological know-how in the wild as exemplified by my students.">school and teaching</a> (66)
</li>
	<li class="cat-item cat-item-11"><a href="http://www.terminally-incoherent.com/blog/category/technology/" title="Rants and musings about technology. Here is where I file my general thoughts about the field of technology, wonder where it is heading, what new advancements await us, talk about gadgets and rage about stupid design or infuriating tech support calls.">technology</a> (916)
<ul class='children'>
	<li class="cat-item cat-item-19"><a href="http://www.terminally-incoherent.com/blog/category/technology/programming/" title="Programming howto's, tutorials, informative posts about my open source (and closed source) projects, software engineering and design discussions and rants about the industry as a whole.">programming</a> (240)
</li>
	<li class="cat-item cat-item-232"><a href="http://www.terminally-incoherent.com/blog/category/technology/sysadmin/" title="This is my Sysadmin knowledge base. When I started this blog, I used to use it as my personal knowledge base. Every time I learned something new in my IT related jobs and projects, I would post about it so that I could easily find it later. The blog became my in-silico exomemory of sorts. Here is where I file my Linux and Unix related articles as well as Windows centric posts.">sysadmin notes</a> (270)
</li>
</ul>
</li>
	<li class="cat-item cat-item-1"><a href="http://www.terminally-incoherent.com/blog/category/archived/" title="This category includes posts written prior to my 2006 move to Wordpress. Originally this blog was hosted with Blogger which at the time did not have fancy features such as tags or categories. ">uncategorized archives</a> (410)
</li>
		</ul>
</li><li id="tag_cloud-4" class="widget-container widget_tag_cloud"><h3 class="widget-title"> Tags</h3><div class="tagcloud"><a href='http://www.terminally-incoherent.com/blog/tag/anime/' class='tag-link-25 tag-link-position-1' title='13 topics' style='font-size: 9.0845070422535pt;'>anime</a>
<a href='http://www.terminally-incoherent.com/blog/tag/avengers/' class='tag-link-266 tag-link-position-2' title='11 topics' style='font-size: 8.3943661971831pt;'>avengers</a>
<a href='http://www.terminally-incoherent.com/blog/tag/bsg/' class='tag-link-55 tag-link-position-3' title='31 topics' style='font-size: 12.633802816901pt;'>bsg</a>
<a href='http://www.terminally-incoherent.com/blog/tag/copyfight/' class='tag-link-8 tag-link-position-4' title='61 topics' style='font-size: 15.394366197183pt;'>copyfight</a>
<a href='http://www.terminally-incoherent.com/blog/tag/fallout-3/' class='tag-link-115 tag-link-position-5' title='11 topics' style='font-size: 8.3943661971831pt;'>fallout 3</a>
<a href='http://www.terminally-incoherent.com/blog/tag/gadgets/' class='tag-link-57 tag-link-position-6' title='31 topics' style='font-size: 12.633802816901pt;'>gadgets</a>
<a href='http://www.terminally-incoherent.com/blog/tag/gigi-edgley/' class='tag-link-46 tag-link-position-7' title='14 topics' style='font-size: 9.3802816901408pt;'>gigi edgley</a>
<a href='http://www.terminally-incoherent.com/blog/tag/hardware/' class='tag-link-62 tag-link-position-8' title='28 topics' style='font-size: 12.140845070423pt;'>hardware</a>
<a href='http://www.terminally-incoherent.com/blog/tag/hax/' class='tag-link-13 tag-link-position-9' title='12 topics' style='font-size: 8.6901408450704pt;'>hax</a>
<a href='http://www.terminally-incoherent.com/blog/tag/howto/' class='tag-link-42 tag-link-position-10' title='40 topics' style='font-size: 13.619718309859pt;'>howto</a>
<a href='http://www.terminally-incoherent.com/blog/tag/internets/' class='tag-link-16 tag-link-position-11' title='89 topics' style='font-size: 16.971830985915pt;'>internets</a>
<a href='http://www.terminally-incoherent.com/blog/tag/java/' class='tag-link-9 tag-link-position-12' title='21 topics' style='font-size: 10.957746478873pt;'>java</a>
<a href='http://www.terminally-incoherent.com/blog/tag/javascript/' class='tag-link-81 tag-link-position-13' title='19 topics' style='font-size: 10.56338028169pt;'>javascript</a>
<a href='http://www.terminally-incoherent.com/blog/tag/latex/' class='tag-link-7 tag-link-position-14' title='29 topics' style='font-size: 12.338028169014pt;'>latex</a>
<a href='http://www.terminally-incoherent.com/blog/tag/lifehack/' class='tag-link-21 tag-link-position-15' title='10 topics' style='font-size: 8pt;'>lifehack</a>
<a href='http://www.terminally-incoherent.com/blog/tag/linux/' class='tag-link-26 tag-link-position-16' title='118 topics' style='font-size: 18.253521126761pt;'>linux</a>
<a href='http://www.terminally-incoherent.com/blog/tag/lost/' class='tag-link-52 tag-link-position-17' title='22 topics' style='font-size: 11.154929577465pt;'>lost</a>
<a href='http://www.terminally-incoherent.com/blog/tag/marvel/' class='tag-link-283 tag-link-position-18' title='13 topics' style='font-size: 9.0845070422535pt;'>marvel</a>
<a href='http://www.terminally-incoherent.com/blog/tag/mobile/' class='tag-link-224 tag-link-position-19' title='23 topics' style='font-size: 11.352112676056pt;'>mobile technology</a>
<a href='http://www.terminally-incoherent.com/blog/tag/mostly-true/' class='tag-link-88 tag-link-position-20' title='20 topics' style='font-size: 10.760563380282pt;'>mostly true</a>
<a href='http://www.terminally-incoherent.com/blog/tag/mysql/' class='tag-link-49 tag-link-position-21' title='11 topics' style='font-size: 8.3943661971831pt;'>mysql</a>
<a href='http://www.terminally-incoherent.com/blog/tag/oblivion/' class='tag-link-92 tag-link-position-22' title='11 topics' style='font-size: 8.3943661971831pt;'>oblivion</a>
<a href='http://www.terminally-incoherent.com/blog/tag/p2p/' class='tag-link-38 tag-link-position-23' title='11 topics' style='font-size: 8.3943661971831pt;'>p2p</a>
<a href='http://www.terminally-incoherent.com/blog/tag/php/' class='tag-link-53 tag-link-position-24' title='25 topics' style='font-size: 11.647887323944pt;'>php</a>
<a href='http://www.terminally-incoherent.com/blog/tag/python/' class='tag-link-83 tag-link-position-25' title='13 topics' style='font-size: 9.0845070422535pt;'>python</a>
<a href='http://www.terminally-incoherent.com/blog/tag/rant/' class='tag-link-203 tag-link-position-26' title='232 topics' style='font-size: 21.112676056338pt;'>rant</a>
<a href='http://www.terminally-incoherent.com/blog/tag/reinventing-fantasy-races/' class='tag-link-261 tag-link-position-27' title='17 topics' style='font-size: 10.169014084507pt;'>reinventing fantasy races</a>
<a href='http://www.terminally-incoherent.com/blog/tag/review/' class='tag-link-36 tag-link-position-28' title='285 topics' style='font-size: 22pt;'>review</a>
<a href='http://www.terminally-incoherent.com/blog/tag/ruby/' class='tag-link-56 tag-link-position-29' title='15 topics' style='font-size: 9.5774647887324pt;'>ruby</a>
<a href='http://www.terminally-incoherent.com/blog/tag/security/' class='tag-link-33 tag-link-position-30' title='63 topics' style='font-size: 15.591549295775pt;'>security</a>
<a href='http://www.terminally-incoherent.com/blog/tag/sf/' class='tag-link-274 tag-link-position-31' title='47 topics' style='font-size: 14.30985915493pt;'>sf</a>
<a href='http://www.terminally-incoherent.com/blog/tag/slacktivism/' class='tag-link-14 tag-link-position-32' title='20 topics' style='font-size: 10.760563380282pt;'>slacktivism</a>
<a href='http://www.terminally-incoherent.com/blog/tag/software-engineering/' class='tag-link-67 tag-link-position-33' title='13 topics' style='font-size: 9.0845070422535pt;'>software engineering</a>
<a href='http://www.terminally-incoherent.com/blog/tag/spam/' class='tag-link-27 tag-link-position-34' title='18 topics' style='font-size: 10.366197183099pt;'>spam</a>
<a href='http://www.terminally-incoherent.com/blog/tag/twitter/' class='tag-link-65 tag-link-position-35' title='16 topics' style='font-size: 9.8732394366197pt;'>twitter</a>
<a href='http://www.terminally-incoherent.com/blog/tag/ubuntu/' class='tag-link-60 tag-link-position-36' title='50 topics' style='font-size: 14.605633802817pt;'>ubuntu</a>
<a href='http://www.terminally-incoherent.com/blog/tag/unix/' class='tag-link-63 tag-link-position-37' title='15 topics' style='font-size: 9.5774647887324pt;'>unix</a>
<a href='http://www.terminally-incoherent.com/blog/tag/video/' class='tag-link-39 tag-link-position-38' title='28 topics' style='font-size: 12.140845070423pt;'>video</a>
<a href='http://www.terminally-incoherent.com/blog/tag/vim/' class='tag-link-87 tag-link-position-39' title='27 topics' style='font-size: 12.042253521127pt;'>vim</a>
<a href='http://www.terminally-incoherent.com/blog/tag/warhammer/' class='tag-link-30 tag-link-position-40' title='23 topics' style='font-size: 11.352112676056pt;'>warhammer</a>
<a href='http://www.terminally-incoherent.com/blog/tag/web-design/' class='tag-link-32 tag-link-position-41' title='63 topics' style='font-size: 15.591549295775pt;'>web design</a>
<a href='http://www.terminally-incoherent.com/blog/tag/windows/' class='tag-link-54 tag-link-position-42' title='111 topics' style='font-size: 17.957746478873pt;'>windows</a>
<a href='http://www.terminally-incoherent.com/blog/tag/wordpress/' class='tag-link-3 tag-link-position-43' title='21 topics' style='font-size: 10.957746478873pt;'>wordpress</a>
<a href='http://www.terminally-incoherent.com/blog/tag/wow/' class='tag-link-85 tag-link-position-44' title='20 topics' style='font-size: 10.760563380282pt;'>wow</a>
<a href='http://www.terminally-incoherent.com/blog/tag/wtf/' class='tag-link-28 tag-link-position-45' title='52 topics' style='font-size: 14.704225352113pt;'>wtf</a></div>
</li><li id="archives-4" class="widget-container widget_archive"><h3 class="widget-title">Archives</h3>		<label class="screen-reader-text" for="archives-dropdown-4">Archives</label>
		<select id="archives-dropdown-4" name="archive-dropdown" onchange='document.location.href=this.options[this.selectedIndex].value;'>
			
			<option value="">Select Month</option>
				<option value='http://www.terminally-incoherent.com/blog/2016/03/'> March 2016  (1)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/10/'> October 2015  (1)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/07/'> July 2015  (1)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/06/'> June 2015  (2)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/05/'> May 2015  (3)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/04/'> April 2015  (2)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/03/'> March 2015  (5)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/02/'> February 2015  (4)</option>
	<option value='http://www.terminally-incoherent.com/blog/2015/01/'> January 2015  (2)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/12/'> December 2014  (4)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/11/'> November 2014  (5)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/10/'> October 2014  (4)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/09/'> September 2014  (5)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/08/'> August 2014  (7)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/07/'> July 2014  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/06/'> June 2014  (6)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/05/'> May 2014  (6)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/04/'> April 2014  (7)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/03/'> March 2014  (7)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/02/'> February 2014  (5)</option>
	<option value='http://www.terminally-incoherent.com/blog/2014/01/'> January 2014  (4)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/12/'> December 2013  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/11/'> November 2013  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/10/'> October 2013  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/09/'> September 2013  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/08/'> August 2013  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/07/'> July 2013  (7)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/06/'> June 2013  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/05/'> May 2013  (10)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/04/'> April 2013  (8)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/03/'> March 2013  (10)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/02/'> February 2013  (11)</option>
	<option value='http://www.terminally-incoherent.com/blog/2013/01/'> January 2013  (14)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/12/'> December 2012  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/11/'> November 2012  (11)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/10/'> October 2012  (11)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/09/'> September 2012  (12)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/08/'> August 2012  (9)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/07/'> July 2012  (12)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/06/'> June 2012  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/05/'> May 2012  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/04/'> April 2012  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/03/'> March 2012  (14)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/02/'> February 2012  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2012/01/'> January 2012  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/12/'> December 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/11/'> November 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/10/'> October 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/09/'> September 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/08/'> August 2011  (16)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/07/'> July 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/06/'> June 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/05/'> May 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/04/'> April 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/03/'> March 2011  (14)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/02/'> February 2011  (12)</option>
	<option value='http://www.terminally-incoherent.com/blog/2011/01/'> January 2011  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/12/'> December 2010  (14)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/11/'> November 2010  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/10/'> October 2010  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/09/'> September 2010  (13)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/08/'> August 2010  (12)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/07/'> July 2010  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/06/'> June 2010  (17)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/05/'> May 2010  (17)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/04/'> April 2010  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/03/'> March 2010  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/02/'> February 2010  (16)</option>
	<option value='http://www.terminally-incoherent.com/blog/2010/01/'> January 2010  (17)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/12/'> December 2009  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/11/'> November 2009  (17)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/10/'> October 2009  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/09/'> September 2009  (17)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/08/'> August 2009  (17)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/07/'> July 2009  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/06/'> June 2009  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/05/'> May 2009  (17)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/04/'> April 2009  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/03/'> March 2009  (18)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/02/'> February 2009  (16)</option>
	<option value='http://www.terminally-incoherent.com/blog/2009/01/'> January 2009  (21)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/12/'> December 2008  (22)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/11/'> November 2008  (20)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/10/'> October 2008  (23)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/09/'> September 2008  (23)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/08/'> August 2008  (22)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/07/'> July 2008  (23)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/06/'> June 2008  (21)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/05/'> May 2008  (22)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/04/'> April 2008  (22)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/03/'> March 2008  (26)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/02/'> February 2008  (27)</option>
	<option value='http://www.terminally-incoherent.com/blog/2008/01/'> January 2008  (30)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/12/'> December 2007  (33)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/11/'> November 2007  (34)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/10/'> October 2007  (34)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/09/'> September 2007  (34)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/08/'> August 2007  (43)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/07/'> July 2007  (42)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/06/'> June 2007  (34)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/05/'> May 2007  (43)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/04/'> April 2007  (36)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/03/'> March 2007  (60)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/02/'> February 2007  (52)</option>
	<option value='http://www.terminally-incoherent.com/blog/2007/01/'> January 2007  (47)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/12/'> December 2006  (39)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/11/'> November 2006  (71)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/10/'> October 2006  (62)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/09/'> September 2006  (58)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/08/'> August 2006  (66)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/07/'> July 2006  (66)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/06/'> June 2006  (52)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/05/'> May 2006  (44)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/04/'> April 2006  (38)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/03/'> March 2006  (31)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/02/'> February 2006  (33)</option>
	<option value='http://www.terminally-incoherent.com/blog/2006/01/'> January 2006  (34)</option>
	<option value='http://www.terminally-incoherent.com/blog/2005/12/'> December 2005  (30)</option>
	<option value='http://www.terminally-incoherent.com/blog/2005/11/'> November 2005  (36)</option>
	<option value='http://www.terminally-incoherent.com/blog/2005/10/'> October 2005  (35)</option>
	<option value='http://www.terminally-incoherent.com/blog/2005/09/'> September 2005  (42)</option>
	<option value='http://www.terminally-incoherent.com/blog/2005/08/'> August 2005  (85)</option>
	<option value='http://www.terminally-incoherent.com/blog/2005/07/'> July 2005  (76)</option>
	<option value='http://www.terminally-incoherent.com/blog/2005/06/'> June 2005  (14)</option>
	<option value='http://www.terminally-incoherent.com/blog/2004/11/'> November 2004  (1)</option>
	<option value='http://www.terminally-incoherent.com/blog/2004/10/'> October 2004  (3)</option>
	<option value='http://www.terminally-incoherent.com/blog/2004/09/'> September 2004  (5)</option>
	<option value='http://www.terminally-incoherent.com/blog/2004/08/'> August 2004  (9)</option>
	<option value='http://www.terminally-incoherent.com/blog/2004/06/'> June 2004  (5)</option>
	<option value='http://www.terminally-incoherent.com/blog/2004/05/'> May 2004  (11)</option>
	<option value='http://www.terminally-incoherent.com/blog/2004/01/'> January 2004  (4)</option>
	<option value='http://www.terminally-incoherent.com/blog/2003/12/'> December 2003  (4)</option>

		</select>
		</li><li id="linkcat-236" class="widget-container widget_links"><h3 class="widget-title">Interesting Blogs</h3>
	<ul class='xoxo blogroll'>
<li><a href="http://bronikowski.com/" title="Smart and funny Polish language technology blog.">Bronikowski</a></li>
<li><a href="http://negativitysandwiches.com/" rel="friend">Negativity Sandwitches</a></li>
<li><a href="http://nullprogram.com/" rel="acquaintance colleague" title="Awesome blog of one of Terminally Incoherent regulars.">Null Program</a></li>
<li><a href="http://www.shamusyoung.com/twentysidedtale/" title="Blog by the guy who made the DM of the Rings, Spoiler Warning and Stolen Pixels.">Twenty Sided</a></li>

	</ul>
</li>
<li id="linkcat-90" class="widget-container widget_links"><h3 class="widget-title">My Software Projects</h3>
	<ul class='xoxo blogroll'>
<li><a href="http://dontspoil.us" rel="me" title="Service for posting hidden spoilers in your tweets.">Don't Spoil Us</a></li>
<li><a href="http://imaginary.pics" rel="me" title="Auto generated gallery of fantasy pictures.">imaginary.pics</a></li>
<li><a href="http://setupassistant.org" rel="me">Luke's Setup Assistant</a></li>
<li><a href="http://markdownjournal.com" rel="me" title="Write personal journal online using Dropbox for storage and Markdown as file format.">Markdown Journal</a></li>
<li><a href="http://pdfMovementTray.com" rel="me" title="Movement tray generator for Warhammer Fantasy.">PDF Movement Tray</a></li>
<li><a href="http://maciak.org/" rel="me" title="My freeware windows utilities.">Personal Projects</a></li>

	</ul>
</li>
<li id="linkcat-89" class="widget-container widget_links"><h3 class="widget-title">People I Know</h3>
	<ul class='xoxo blogroll'>
<li><a href="http://gigiedgleyfansite.com" rel="me">Gigi Edgley Fansite</a></li>
<li><a href="http://www.synthworld.org/" rel="friend met colleague">Synthworld</a></li>
<li><a href="http://xmundane.com" rel="me" title="Xmundane: Paranormal Encounters is a Tabletop Miniatuee Skirmish Game I’m working on.">Xmundane</a></li>

	</ul>
</li>
<li id="linkcat-239" class="widget-container widget_links"><h3 class="widget-title">Web Comics I Read</h3>
	<ul class='xoxo blogroll'>
<li><a href="http://abstrusegoose.com/" title="Like XKCD with slightly better art. Equally awesome.">Abstruse Goose</a></li>
<li><a href="http://dresdencodak.com" title="Amazing artwork, incredible concepts, smart humor. It is a comic about transhumanism, robots and super science.">Dresden Codak</a></li>
<li><a href="http://xkcd.com" title="Shut up, XKCD is awesome and you know it!">XKCD</a></li>

	</ul>
</li>
<li id="linkcat-238" class="widget-container widget_links"><h3 class="widget-title">Web Series I Watch</h3>
	<ul class='xoxo blogroll'>
<li><a href="http://www.errantsignal.com/">Errant Signal</a></li>
<li><a href="http://www.escapistmagazine.com/videos/view/escape-to-the-movies" title="One of my favorite movie review shows.">Escape to the Movies</a></li>
<li><a href="http://www.feministfrequency.com/">Feminist Frequency</a></li>
<li><a href="http://www.heyash.com/" title="Hey Ash, Whatcha Playin’">HAWP</a></li>
<li><a href="http://www.escapistmagazine.com/videos/view/loadingreadyrun" title="Geejy sketch comedy series. Very funny.">Loading Ready Run</a></li>
<li><a href="http://thatguywiththeglasses.com/videolinks/team-nchick/nostalgia-chick" title="Hilarious movie reviews made by former film student and an adorable full time sociopath. ">The Nostalgia Chick</a></li>
<li><a href="http://www.escapistmagazine.com/videos/view/zero-punctuation" title="Best video game review series. Period.">Zero Punctuation</a></li>

	</ul>
</li>
<li id="meta-3" class="widget-container widget_meta"><h3 class="widget-title">Meta</h3>			<ul>
						<li><a href="http://www.terminally-incoherent.com/blog/wp-login.php">Log in</a></li>
			<li><a href="http://www.terminally-incoherent.com/blog/feed/">Entries <abbr title="Really Simple Syndication">RSS</abbr></a></li>
			<li><a href="http://www.terminally-incoherent.com/blog/comments/feed/">Comments <abbr title="Really Simple Syndication">RSS</abbr></a></li>
			<li><a href="https://wordpress.org/" title="Powered by WordPress, state-of-the-art semantic personal publishing platform.">WordPress.org</a></li>			</ul>
			</li>			</ul>
		</div><!-- #secondary .widget-area -->


</div> <!-- ti-nav -->	</div><!-- #main -->

	<div id="footer" role="contentinfo">
		<div id="colophon">



			<div id="site-info">
				<a href="http://www.terminally-incoherent.com/blog/" title="Terminally Incoherent" rel="home">
					Terminally Incoherent				</a>
			</div><!-- #site-info -->

			<div id="site-generator">
								<a href="http://wordpress.org/"
						title="Cat Picture Publishing Platform" rel="generator">
					Proudly powered by Monkeys.				</a>
			</div><!-- #site-generator -->

		</div><!-- #colophon -->
	</div><!-- #footer -->

</div><!-- #wrapper -->

<script type='text/javascript' src='http://www.terminally-incoherent.com/blog/wp-content/plugins/akismet_/_inc/form.js?ver=3.3'></script>
<script type='text/javascript' src='http://www.terminally-incoherent.com/blog/wp-includes/js/wp-embed.min.js?ver=4.7.17'></script>
</body>
</html>