﻿<rss version="2.0">
<channel>
    <title>subdimension.co.uk blog feed</title>
    <link>http://subdimension.co.uk/</link>
    <description/>
    <copyright>All rights reserved.</copyright>
    <language>en-GB</language>
    <lastBuildDate>Thu, 17 May 2012 12:05:33 BST</lastBuildDate>
    <ttl>1440</ttl>
    
    <item>
        <title><![CDATA[Multiple Flask applications in one package]]></title>
        
        <link>http://subdimension.co.uk/2012/05/14/Multiple_Flask_applications_in_one_package.html</link>
        <guid>http://subdimension.co.uk/2012/05/14/Multiple_Flask_applications_in_one_package.html</guid>
        
        <description><![CDATA[
        <p>I'm almost ready to deploy pyDimension to my live server. I mentioned previously that I'd been working on the search indexer - every time a new post is saved, I break it up into a list of words and add the post's url to a file named for each word.</p>
<p>The other side of the process is taking a search string, breaking it down into words and finding all of the posts that match the terms. I even have my own PageRank algorithm:</p>
<pre><code>@srch.route('/', methods=['GET', 'POST'])
def search():
    if request.method == 'POST':
        terms = get_words(request.form['search_string'])
    else:
        terms = get_words(request.args.get('s', ''))
    results = []
    for t in terms:
        indexFilename = "%s/%s" % (app.config['SEARCH_INDEX_DIR'], t)
        if os.path.exists(indexFilename):
            indexFile = codecs.open(indexFilename, encoding='utf-8', mode='r')
            results = indexFile.read().splitlines() + results
            indexFile.close()

    result_set = set(results)

    ranked_results = []
    for r in result_set:
        ranked_results.append((results.count(r), r, os.path.split(r)[1]))

    ranked_results.sort()
    ranked_results.reverse()

    return render_template('%s/search_results.html' % app.config['SITE_TEMPLATE'],
                       terms=" ".join(terms),
                       results=ranked_results)
</code></pre>
<p>This is the only part of my website that isn't served as static HTML. I guess in theory I could create a search results page for every possible word combination, but that would be stupid.</p>
<p>I ran into a problem though. The main application runs on a special subdomain behind a login, separate to the main site you're reading now. The search part though is accessed anonymously and from the main site domain.</p>
<p>I wasn't sure at first how to do this with Flask - I knew that I wanted to keep everything all neatly within the same package though, as it all belongs together.</p>
<p>My inexperience with Python lead me to creating a <code>.wsgi</code> file in the static site that <em>only</em> imported the <code>pydimension.search</code> module. I thought that would be quite clever, however, it still imports the whole of the Flask app, so I just ended up with a new entrypoint to the admin application.</p>
<p>After briefly giving up, I realised that the <code>app</code> object in my package was really just an arbitrary instantiation of the Flask class. I could create a new one and just add the <code>/search</code> route to it.</p>
<p>I modified the package <code>__init__.py</code> file:</p>
<pre><code>from flask import Flask

app = Flask(__name__)
app.config.from_pyfile('site_settings.cfg')

srch = Flask(__name__)
srch.config.from_pyfile('site_settings.cfg')

import pyDimension.system
import pyDimension.access_control
import pyDimension.views
import pyDimension.search
</code></pre>
<p>then I changed the decorator for the <code>search()</code> function as you can see above. Initially, I routed it like this: <code>@srch.route('/search', methods=['GET', 'POST'])</code> and started my WSGI Daemon at the root of the domain, but I found that caused <code>mod_wsgi</code> to handle all request to the domain and just return 404 errors for everything but <code>/search</code>, instead, when I configured Apache, I started the daemon at <code>/search</code>:</p>
<pre><code>&lt;VirtualHost *:80&gt;
    ServerName flasktest.subdimension.co.uk

    WSGIDaemonProcess flaskTest user=flask group=www-data threads=5 home=/&lt;redacted&gt;/searchTest
    WSGIScriptAlias /search /&lt;redacted&gt;/flaskTest/searchTest.wsgi

    &lt;Directory /&lt;redacted&gt;/searchTest&gt;
        WSGIProcessGroup searchTest
        WSGIApplicationGroup %{GLOBAL}
        WSGIScriptReloading On
        Order deny,allow
        Allow from all
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;
</code></pre>
<p>In the <code>.wsgi</code> file for the static site domain, I imported <code>srch</code> from <code>pyDimension</code> instead of <code>app</code>:</p>
<pre><code>import sys

sys.path.append('/redacted/pyDimension')

from pyDimension import srch as application
</code></pre>
<p>It worked!</p>
        ]]></description>
        <pubDate>Mon, 14 May 2012 18:54:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[Python FAQ: Webdev]]></title>
        
        <link>http://me.veekun.com/blog/2012/05/05/python-faq-webdev/</link>
        <guid isPermaLink="false">http://subdimension.co.uk/2012/05/09/Python_FAQ_Webdev.html</guid>
        
        <description><![CDATA[
        <p>Following his recent trashing of PHP, Eevee has posted a getting started guide for Python web development.</p>
        ]]></description>
        <pubDate>Wed, 09 May 2012 13:31:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[Here&#39;s a fun one]]></title>
        
        <link>http://subdimension.co.uk/2012/05/08/Heres_a_fun_one.html</link>
        <guid>http://subdimension.co.uk/2012/05/08/Heres_a_fun_one.html</guid>
        
        <description><![CDATA[
        <p>In my <a href="http://subdimension.co.uk/2012/05/05/Python_equivalent_to_PHPs_strwordcount.html">last post</a> I created a function to return all of the words from a blog post. I stripped out all of the 'non-word' characters with this:</p>
<pre><code>PUNCTUATION = "#$%&amp;"'()*+,./:;&lt;=&gt;!?@[]^`{|}~"

def get_words(text):
    depunc = text.lower()
    for p in PUNCTUATION:
        depunc = depunc.replace(p, ' ')
</code></pre>
<p>and it worked fine on all 3 machines that I use variously throughout the day, one OS X, one Windows 7 and one Ubuntu. Yesterday I <a href="http://subdimension.co.uk/2012/04/24/Deploying_Flask_to_Apache.html">deployed to Apache</a> for the next stage of testing and the whole thing fell over!</p>
<p>Flask isn't much help when it isn't running through its development server so it took me a <em>lot</em> of poking around to work out what was going on. Finally, I worked out that it was falling over at <a href="http://subdimension.co.uk/2012/04/10/This_essay_makes_me_feel_stupid.html">the post that started all of this off</a>. The quote I had copied and pasted:</p>
<blockquote>
<p>If you only know PHP and you’re curious to learn something else, give the Python tutorial a whirl and try Flask for the web stuff.</p>
</blockquote>
<p>it has a 'smart quote' in it for the word you're. I'm lazy and I've never bothered to use them myself, but lots of people use them and they do pop up in a couple more of my posts where I've copied quotes. The Flask development server didn't care about them at all, it happily created index files with these characters in the name - I had noticed but decided I didn't care just yet.</p>
<p>Once I deployed through <code>mod_wsgi</code>, this didn't work any more. I have no idea why and I'm not sure where to start finding out either. Instead, I modified my <code>get_words()</code> function:</p>
<pre><code>depunc = re.sub('[^a-z_\-\s]', ' ', text.lower())
</code></pre>
<p>It probably even runs faster since it's using the regex module (written in C I believe), rather than iterating around characters in a string. I did think about adding the various other characters to the <code>PUNCTUATION</code> constant, but since they're Unicode characters, I knew I'd never get them all, so I just filter out everything that is not recognisable as a word that I have written. I will revisit this one day, if I ever find myself writing Arabic or French! </p>
        ]]></description>
        <pubDate>Tue, 08 May 2012 09:56:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[Python equivalent to PHP&#39;s str word count]]></title>
        
        <link>http://subdimension.co.uk/2012/05/05/Python_equivalent_to_PHPs_strwordcount.html</link>
        <guid>http://subdimension.co.uk/2012/05/05/Python_equivalent_to_PHPs_strwordcount.html</guid>
        
        <description><![CDATA[
        <p>I've been really enjoying learning Python. I'm discovering that it has plenty of its own little foibles, but on balance, seems to make a bit more sense than PHP (list slices are <em>so</em> much easier to remember than <code>substr()</code> or is it <code>substring()</code> or <code>sub_str()</code>?!)</p>
<p>I'm still working on my Pythonic Static Blogging system, dubbed pyDimension for want of inspiration, and just finished implementing my <a href="http://subdimension.co.uk/2012/04/09/Static_Text_Search.html">static search indexing system</a>. Python didn't come with an obvious way to generate a list of word from a body of text like PHP does, so I had to implement my own.</p>
<p>In PHP, I generated a list of words to use as indexes with this:</p>
<pre><code>$words = array_unique(
    str_word_count(
        strtolower(
            str_replace("'", '', 
                strip_tags($text)))
    , 1));
</code></pre>
<p>When you pass the <code>1</code> parameter to PHP's <code>str_word_count()</code> it returns an array of words and it's quite clever about what it considers a word.</p>
<p>I couldn't find anything obvious in Python's Standard Library, so with a little bit of tinkering, I came up with this:</p>
<pre><code>PUNCTUATION = "#$%&amp;"'()*+,./:;&lt;=&gt;!?@[]^`{|}~"

def get_words(text):
    depunc = text.lower()
    for p in PUNCTUATION:
        depunc = depunc.replace(p, ' ')

    all_words = set(depunc.split())
    words = []

    for w in all_words:
        if len(w) &gt; 2 and not w.isdigit():
            words.append(w.strip('-'))

    return words
</code></pre>
<p>The <code>string</code> package has a punctuation constant, but I wanted to keep underscores and hyphens, that way <code>get_words</code> would be indexed as the full method name, rather than 'get' and 'words', which is less meaningful. I also decided to exclude words of one or two characters, and numbers. Ideally I wanted to keep numbers, as they are useful - for example, in my post about Dropbox LanSync, indexing the port number would be useful. However, because of the way I remove punctuation, numbers with significant formatting, such as dates or times wouldn't be indexed properly, so I decided to leave them out.</p>
<p>Once I have a list of all the words in a post or page, I add the URL of that item to a list stored in a file named for each word.</p>
<h3>Update</h3>
<p>This doesn't work when it's running through <code>mod_wsgi</code> so <a href="http://subdimension.co.uk/2012/05/08/Heres_a_fun_one.html">I changed it</a></p>
        ]]></description>
        <pubDate>Sat, 05 May 2012 20:23:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[Dropbox LANSync]]></title>
        
        <link>http://subdimension.co.uk/2012/04/28/Dropbox_LANSync.html</link>
        <guid>http://subdimension.co.uk/2012/04/28/Dropbox_LANSync.html</guid>
        
        <description><![CDATA[
        <p>I had a friendly email from my VPS provider this morning. It had been reported that my server was port scanning other servers and that I needed to do something about it.</p>
<p>My first instinct was to panic: "OMG, I've been hacked and I'm spewing spam from my server, I should never have thought I was ever capable of running these things myself".</p>
<p>This was shortly replaced with "Hmm, that port number looks oddly 'reasonable', it must be something 'friendly'." I took a shot in the dark and Googled "Dropbox port 17500". Hits! Phew!</p>
<p>Dropbox has a nifty feature called LANSync, where it will try to find other machines on your local network and sync with them. It's a clever idea that means you're not forever sending the same files up and down the internet when you use it on multiple machines (and after all, what's the point of Dropbox if not to keep multiple machines in sync!).</p>
<p>I knew about this and had naively thought that it would just get ignored in a VPS environment - I'm a bit ignorant of how it all works really.</p>
<p>Apparently not though. My server has been spamming my VPS neighbours every 30 seconds for the last few weeks. Whoops!</p>
<p>After looking through the documentation I linked in my <a href="http://subdimension.co.uk/2012/04/08/Syncing_Dropbox_on_Linux.html">post about setting this all up in the first place</a>, I noticed a distinct lack of any help with switching off LANSync from the command line - the links they provide are all broken now (nice going!).</p>
<p>Luckily I'd taken the time to install the Dropbox CLI, so I ran it:</p>
<pre><code>$  ./bin/dropbox.py

Dropbox command-line interface

commands:

Note: use dropbox help &lt;command&gt; to view usage for a specific command.

 status       get current status of the dropboxd
 help         provide help
 puburl       get public url of a file in your dropbox
 stop         stop dropboxd
 running      return whether dropbox is running
 start        start dropboxd
 filestatus   get current sync status of one or more files
 ls           list directory contents with current sync status
 autostart    automatically start dropbox at login
 exclude      ignores/excludes a directory from syncing
 lansync      enables or disables LAN sync
</code></pre>
<p>Ah hah! Last one looks like the ticket:</p>
<pre><code>$  ./bin/dropbox.py lansync

enables or disables LAN sync
dropbox lansync [y/n]

options:
  y  dropbox will use LAN sync (default)
  n  dropbox will not use LAN sync

$  ./bin/dropbox.py lansync n
</code></pre>
<p>hmm, no feedback, always helpful!</p>
<p>I've never really been a belt-and-braces kind of person, but I don't want to get shut down by my VPS provider, so a bit more research turned up <a href="http://help.ubuntu.com/community/IptablesHow-To">iptables</a> which is installed on all Ubuntu releases.</p>
<pre><code>$  /sbin/iptables -A OUTPUT -p udp --dport 17500 -j DROP
$  iptables-save
</code></pre>
<p>which should block it from getting out if it ever starts up again. Sorted.</p>
        ]]></description>
        <pubDate>Sat, 28 Apr 2012 12:21:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[Found it!]]></title>
        
        <link>http://subdimension.co.uk/2012/04/26/Found_it.html</link>
        <guid>http://subdimension.co.uk/2012/04/26/Found_it.html</guid>
        
        <description><![CDATA[
        <p>I worked out one of the reasons my <a href="http://subdimension.co.uk/2012/04/20/New_Project_pyDimension.html">nasty little function</a> is bad:</p>
<pre><code>if '/' in sPath:
    ...
    month = [sPath.split('/')[1] for x in range(len(days))]
</code></pre>
<p><code>sPath</code> was built by walking down into a directory and I used it to work out if I was looking at directories for days or months. This little fragment of code worked fine, until today, when I was running it on my Windows machine.</p>
<p>Windows uses the \ character to separate directories, Python knows this and it helpfully provides a set of functions that deal with it automatically. I just didn't use them:</p>
<pre><code>if os.path.split(sPath)[0] != '':
    ...
    month = [os.path.split(sPath)[1] for x in range(len(days))]
</code></pre>
<p><code>os.path</code> knows all about directory separators.</p>
        ]]></description>
        <pubDate>Thu, 26 Apr 2012 20:43:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[Deploying Flask to Apache]]></title>
        
        <link>http://subdimension.co.uk/2012/04/24/Deploying_Flask_to_Apache.html</link>
        <guid>http://subdimension.co.uk/2012/04/24/Deploying_Flask_to_Apache.html</guid>
        
        <description><![CDATA[
        <p>I'm still slowly plugging away with my new Python + flask static blogging system. I thought I'd share the details of my most recent exploration.</p>
<p>Out of the box, the flask framework comes with its own development server, so far I've only used this but eventually I would be deploying it onto my main server running apache. I decided I needed to make sure I could get this working without too much trouble so I made a simple Hello World application along the lines of the main <a href="http://flask.pocoo.org/docs/quickstart/#quickstart">flask tutorial</a>.</p>
<p>I uploaded it to a folder on my server and SSH'd in to start configuring things. I'd already set up a sub domain to use and pointed it at the server too.</p>
<p>I found that the flask <a href="http://flask.pocoo.org/docs/deploying/mod_wsgi/">documentation for setting up with Apache</a> made the assumption that you'd already worked with WSGI applications and just needed the flask way so it took a bit of digging and re-reading.</p>
<p>First, I <a href="http://superuser.com/questions/77617/how-can-i-create-a-non-login-user">created a new shell-less user</a> to run my app as. I could have just used the Apache user (www-data), but I wanted to be able to distinguish files that had been molested by my flask app:</p>
<p>create a new user without a home directory:</p>
<pre><code>$  useradd -M flask
</code></pre>
<p>remove shell:</p>
<pre><code>$  usermod -s /bin/false flask
</code></pre>
<p>Finally, lock the account to prevent logging in:</p>
<pre><code>$  usermod -L flask
</code></pre>
<p>I also added the user to the Apache <code>www-data</code> group. This makes it easier to work with file permissions later as I can keep ownership of them with my own user, but allow the <code>www-data</code> group access.</p>
<pre><code>$  adduser flask www-data
</code></pre>
<p>Next I created <code>flaskTest.wsgi</code> in my application's folder:</p>
<pre><code>import sys

sys.path.append('/&lt;redacted&gt;/flaskTest')

from flaskTest import app as application
</code></pre>
<p>Initially I didn't think I needed to import the path to the app as I assumed it would be started in its own directory, however, I realised later that <code>mod_wsgi</code> starts apps with the same base directory a Apache, which in my case was the system root.</p>
<p>Finally I created a new Apache VirtualHost config in <code>/etc/apache2/sites-available</code>:</p>
<pre><code>&lt;VirtualHost *:80&gt;
    ServerName flasktest.subdimension.co.uk

    WSGIDaemonProcess flaskTest user=flask group=www-data threads=5 home=/&lt;redacted&gt;/flaskTest
    WSGIScriptAlias / /&lt;redacted&gt;/flaskTest/flaskTest.wsgi

    &lt;Directory /&lt;redacted&gt;/flaskTest&gt;
        WSGIProcessGroup flaskTest
        WSGIApplicationGroup %{GLOBAL}
        WSGIScriptReloading On
        Order deny,allow
        Allow from all
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;
</code></pre>
<p>Adding the <code>WSGIScriptReloading</code> directive meant that any time I made changes to my application files, I could simply <code>$  touch flaskTest.wsgi</code> and the app would restart with changes applied. I even found a <a href="http://stackoverflow.com/questions/1158076/implement-touch-using-python">python-based implementation of touch</a> that I could use if I wanted to be able to restart the app from within the app!</p>
<p>As mentioned above, by default, the application starts as if it were called from the server root. The <a href="https://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIDaemonProcess">WSGI Documentation</a> gave me the extra <code>home</code> option for the <code>WSGIDaemonProcess</code> directive, which changes that to wherever you want it.</p>
<p>I had to make one final change to the configuration file from the documented example, which was to specify the port for the VirtualHost <code>*:80</code>. Without this it over-rode my other virtual hosts and everything ended up pointing to the same place!</p>
<p>With all this done, enable the site and restart of Apache:</p>
<pre><code>$  a2ensite flaskTest
$  /etc/init.d/apache2 reload
</code></pre>
<p>and my application was running!</p>
        ]]></description>
        <pubDate>Tue, 24 Apr 2012 22:20:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[Fiddling with fonts]]></title>
        
        <link>http://subdimension.co.uk/2012/04/23/Fiddling_with_fonts.html</link>
        <guid>http://subdimension.co.uk/2012/04/23/Fiddling_with_fonts.html</guid>
        
        <description><![CDATA[
        <p>I decided I preferred the look of a serif font, I was also bothered by the fact that my previous font didn't have bold or italic variants, making it hard to <em>emphasise</em> things.</p>
<p>My new font is <a href="http://www.fontsquirrel.com/fonts/gandhi-serif">Gandhi serif</a>.</p>
<p>I'll let it sit for a while and see how I like it in a few days.</p>
        ]]></description>
        <pubDate>Mon, 23 Apr 2012 12:28:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[New Project: pyDimension]]></title>
        
        <link>http://subdimension.co.uk/2012/04/20/New_Project_pyDimension.html</link>
        <guid>http://subdimension.co.uk/2012/04/20/New_Project_pyDimension.html</guid>
        
        <description><![CDATA[
        <p>As I mentioned previously, I've always found PHP awkward to use but I'd always kind of put it into the whole "PHP is the worst web development language there is; apart from all the others" sort of a box.</p>
<p>I'd always been interested in Python, but I find it very difficult to learn something by following through a tutorial. I need to have a project to get stuck in to and the few ideas I have had were always a little over-ambitious.</p>
<p>Fresh from my win with my search system (and freshly deflated by The Essay), I decided to give <a href="http://flask.pocoo.org">Flask</a> a go.</p>
<p><em>BEST. THING. EVER!</em></p>
<p>I am so impressed with how quickly I got to grips with Flask, Jinja2 and of course Python. In the last 4 days I've almost completely replicated my staticDimension blog publishing system in Python!</p>
<p>It's still a long way from being shareable in full (I'm not even using it to write this yet, it's still safely locked away in my testing environment) but I wanted to share a small bit of the problem I was up until midnight last night solving.</p>
<p>At the point I wrote this bit, I was feeling <em>really</em> pleased with myself that I'd managed to transition into a whole new language and was already writing simple, efficient, beautiful code. I needed to implement the code for generating the archive pages that make the post date directories browsable.</p>
<p>This is what I came up with:</p>
<pre><code>def rebuild_archive_indexes():
    archiveYearDirs = glob("%s/[0-9][0-9][0-9][0-9]" % app.config['SITE_ROOT_DIR'])
    archiveFile = codecs.open("%s/archive.html" % (app.config['SITE_ROOT_DIR']),
                                               encoding='utf-8', mode='w')

    archiveYears = []                          
    for y in archiveYearDirs:
        year = os.path.split(y)[1]
        archiveYears.append((year, year))

        for (path, dirs, files) in os.walk("%s/%s" % (app.config['SITE_ROOT_DIR'], year)):
            archiveIndex = codecs.open("%s/index.html" % path, encoding='utf-8', mode='w')

            sPath = path[len(app.config['SITE_ROOT_DIR'])+1:]
            if not dirs:
                files.remove('index.html')
                items = map(get_full_title, files)
            else:
                if '/' in sPath:
                    days = dirs
                    month = [sPath.split('/')[1] for x in range(len(days))]
                    yr = [year for x in range(len(days))]
                    items = map(day_name, days, month, yr)
                else:
                    items = map(month_name, dirs)

            archiveIndex.write(render_template("%s/archive.html" % app.config['SITE_TEMPLATE'],
                                          site_root_url=app.config['SITE_ROOT_URL'], 
                                          items=items,
                                          breadcrumb=sPath+'/'))
            archiveIndex.close()

    archiveFile.write(render_template("%s/archive.html" % app.config['SITE_TEMPLATE'],
                               site_root_url=app.config['SITE_ROOT_URL'], 
                               items=archiveYears,
                               breadcrumb=""))
    archiveFile.close()
    flash('Archive rebuilt')
    return redirect(url_for('control_panel'))

def month_name(monthNumber):
    months = ("January", "February", "March", 
                     "April", "May", "June", "July", 
                     "August", "September", "October", 
                     "November", "December")
    return (monthNumber, months[int(monthNumber)-1])

def day_name(dayNumber, month, year):
    d = datetime.date(int(year), int(month), int(dayNumber))
    return (dayNumber, "%s, %s" % (d.strftime('%A'), dayNumber))

def get_full_title(f):
    return (f, f)
</code></pre>
<p>I'm pretty sure this would be classified as an abomination and get me kicked out of any Python clubs - <code>month = [sPath.split('/')[1] for x in range(len(days))]</code> to generate a list of the same values just so I can use <code>map()</code> on the days tuple to work out what day the 1st of May was in 2011. <code>get_full_title()</code> isn't fully implemented yet and may well spawn a post of its own too - the lazy method means opening and reading every plaintext blog post (again) just to get the first line. I'm pretty sure I can do better than that.</p>
        ]]></description>
        <pubDate>Fri, 20 Apr 2012 21:40:00 GMT</pubDate>
    </item>
    
    <item>
        <title><![CDATA[This essay makes me feel stupid]]></title>
        
        <link>http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/</link>
        <guid isPermaLink="false">http://subdimension.co.uk/2012/04/10/This_essay_makes_me_feel_stupid.html</guid>
        
        <description><![CDATA[
        <p>I read through this essay, nodding and smiling most of the way (not all because I was only pretending I knew what he was talking about).</p>
<p>There's a section of code from my post yesterday that left me feeling uncomfortable:</p>
<pre><code>    $results = array_count_values($results);
    asort($results);
    $results = array_reverse($results);
</code></pre>
<p>The middle line was <code>$results = asort($results);</code> the first time around. I always go there first, until I remember that <code>asort()</code> does the sort 'in place'. It's always grated on me.</p>
<p>I was pleased that "eevee" closed with this:</p>
<blockquote>
<p>If you only know PHP and you’re curious to learn something else, give the Python tutorial a whirl and try Flask for the web stuff.</p>
</blockquote>
<p>I quite like the look of <a href="http://flask.pocoo.org/">Flask</a>, I might give it a go.</p>
        ]]></description>
        <pubDate>Tue, 10 Apr 2012 19:56:00 GMT</pubDate>
    </item>
    
</channel>
</rss>
