I have been using Mutt for a while now. Wouldn’t say that it saves my time, but nor does it extend the amount of time I spend reading email. For me, the best part about Mutt is that it lets me use text editor of my choice - Vim. Everything else - keyboard shortcuts, minimalist design, and simplicity - already exists in Gmail.

I found configuration below to work really well for my needs: all of the important for me Gmail features are translated. Here’s my .muttrc file:

bind editor <space> noop
set alias_file        = '~/.mutt/aliases.txt'
set copy              = no
set display_filter    = '$HOME/.mutt/aliases.sh'
set edit_headers
set editor            = "vim +/^$ ++1"
set folder            = "imaps://imap.gmail.com/"
set hostname          = "gmail.com"
set imap_check_subscribed
set imap_pass         = "$PASSWORD"
set imap_user         = "$USERNAME"
set mail_check        = 5
set move              = no
set postponed         = "+[Gmail]/Drafts"
set spoolfile         = "+INBOX"
set text_flowed       = yes
unset imap_passive
unset record

# Gmail-style keyboard shortcuts
macro index ga "<change-folder>=[Gmail]/All Mail<enter>" "Go to all mail"
macro index gd "<change-folder>=[Gmail]/Drafts<enter>" "Go to drafts"
macro index gi "<change-folder>=INBOX<enter>" "Go to inbox"
macro index gs "<change-folder>=[Gmail]/Starred<enter>" "Go to starred messages"
macro index gt "<change-folder>=[Gmail]/Trash<enter>" "Go to trash"
macro index,pager d "<save-message>=[Gmail]/Trash<enter><enter>" "Trash"
macro index,pager y "<save-message>=[Gmail]/All Mail<enter><enter>" "Archive"

source $alias_file

It is quite self-explanatory, and includes such nice features as:

  • Automatically adding addresses from read emails to address book (see below).
  • Using vim as a text editor, with an ability to edit message headers/recipients from within vim.
  • Ability to access all the default Gmail folders: All mail, Drafts, Inbox, Starred, Trash.
  • Key bindings to delete and archive messages bound to d and y respectfully (I am a huge fun of a zero-mail inbox).

You might also want to have your password encrypted by GPG as opposed to leaving it in plain text in your .muttrc file. You can read how to do this here: Using Mutt with GPG.

As you may have noticed, .muttrc above sets display_filter to $HOME/.mutt/aliases.sh. This script is being executed every time you read an email, and it collects email address to $HOME/.mutt/aliases.txt. Contents of the aliases.sh are below:

#!/bin/sh

MESSAGE=$(cat)

NEWALIAS=$(echo "${MESSAGE}" | grep ^"From: " | sed s/[\,\"\']//g | awk '{$1=""; if (NF == 3) {print "alias" $0;} else if (NF == 2) {print "alias" $0 $0;} else if (NF > 3) {print "alias", tolower($(NF-1))"-"tolower($2) $0;}}')


if grep -Fxq "$NEWALIAS" $HOME/.mutt/aliases.txt; then
    :
else
    echo "$NEWALIAS" >> $HOME/.mutt/aliases.txt
fi

echo "${MESSAGE}"

Source: W. Caleb McDaniel.

This script will create aliases.txt file containing email addresses for search and auto completion of email-addresses.

Mutt is a great command line email client, but it does not offer a built-in way to store passwords. But that’s where GPG comes in. A while back I wrote an article on how to use GPG to store your passwords: GPG Usage, this is a more practical note about using GPG to store your passwords for mutt. This note implies that you already have installed and configured GPG (which you can learn how to do in above linked article).

First you will have to record a password to a GPG file. Replace $PASSWORD with your password and $ACCOUNT with a desired account alias. You probably want to prefix this command with a space, to avoid writing your password to a history file.

echo '$PASSWORD' | gpg --use-agent -e > ~/.passwd/$ACCOUNT.gpg

Next, open your ~/.muttrc file and add following line:

set imap_pass = "`gpg --use-agent --quiet --batch -d ~/.passwd/$ACCOUNT.gpg`"

Again, replace $ACCOUNT with the same account alias you specified earlier. Now you don’t have to re-enter your password every time you start Mutt.

Few years ago I used a lightweight IDE called “Sublime Text 2”. And one of the most compelling features of it was an ability to switch between files by hitting Ctrl + P and typing in a part of the file name.

This is exactly what ctrlp.vim implements in vim. Usage is extremely simple: install the plugin, hit Ctrl + P, type a part of the file name, select a desired file and hit Enter. You can use arrow keys or Ctrl + J and Ctrl + K to navigate between suggested file list.

Source: https://github.com/kien/ctrlp.vim.

Lettuce is a great BDD tool which allows you to parse expressions written via Gherkin syntax in python. However the documentation is not very comprehensive, and at the moment current version (0.2.19) has some issues integrating with the latest Django (1.6.1 at the moment of writing). Without further ado, I’ll get to a comprehensive tutorial.

Let’s assume you are using pip and virtualenv for the dependency control, and you already have a working project configured. Your project is called “myproject”, and the only app you have within your project is called “polls”.

Setup

First, you have to install lettuce library. At the moment of writing, current released version (0.2.19) has an error integrating with Django, so we’ll install current development version. Releases 0.2.20 and up should include the fix, so pip install lettuce would be better if the version is out.

pip install -e \
    git://github.com/gabrielfalcao/lettuce@cccc397#egg=lettuce-master
pip install django-nose splinter
pip freeze > requirements.txt

First line downloads lettuce package from the github repository and installs missing dependencies. You can replace cccc397 with the current commit. Technically commit can be omitted, but we don’t want to have an unstable ever-changing branch in our requirements.txt. I also added django-nose since nose assertions come in handy while writing Lettuce steps, as well as splinter, which is a great tool for testing web application.

Add Lettuce to the INSTALLED_APPS in your myproject/settings.py:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'django.contrib.admindocs',
    # ... third party apps ...
    'lettuce.django',
    'myproject',
    'polls',
)

You also have to explicitly specify the apps you want to use with lettuce:

LETTUCE_APPS = (
    'polls',
)

By default, lettuce will run its’ tests against your default database. But we want to use test database for that, so we have to add few more settings:

LETTUCE_TEST_SERVER = 'lettuce.django.server.DjangoServer'
LETTUCE_SERVER_PORT = 9000

Where LETTUCE_TEST_SERVER is a subclass of Django’s LiveTestServerCase - a class which runs a test server for you and LETTUCE_SERVER_PORT is different from port 8000 so you won’t have issues running the development server via python manage.py runserver at the same time as running Lettuce tests.

You also have to create a features directories inside the apps you want to test with Lettuce:

/myproject
    /myproject
        __init__.py
        settings.py
        urls.py
        wsgi.py
    /polls
        /features
            /steps
                __init__.py
                polls_list.py
            polls_list.feature
        __init__.py
        models.py
        tests.py
        views.py
    manage.py
    requirements.txt
    terrain.py

Lettuce has its’ own settings file called terrain.py. It has to be in the same directory as a manage.py:

from django.core.management import call_command
from django.conf import settings
from lettuce import before, after, world
from splinter.browser import Browser

@before.each_scenario
def flush_database(scenario):
    call_command('flush', interactive=False, verbosity=0)

@before.each_scenario
def prepare_browser(scenario):
    world.browser = Browser()

@after.each_scenario
def destroy_browser(scenario):
    world.browser.quit()

This code flushes the database before each scenario, as well as prepares and destroys the splinter browser.

Writing the features

Feature files support standard Gherkin syntax, let’s write one right now in polls/features/polls_list.feature:

Feature: Polls list

    Scenario: Polls list without any polls
        When I access the "polls list" url
        Then I see a text "We didn't find any polls!"

    Scenario: Polls list with one poll
        Given a poll with the title "Hello world"
        When I access the "polls list" url
        Then I see a text "Hello world"
        And I do not see a text "We didn't find any polls!"

Now describe the steps in polls/features/steps/polls_list.py:

from django.core.urlresolvers import reverse
from lettuce import step, world
from lettuce.django import django_url
from nose.tools import assert_in, assert_not_in
from polls.models import Poll

PAGES = {
    "polls list": "polls:list"
}

@step(r'access the "(.*)" url')
def access_the_url(step, name):
    world.browser.visit(django_url(reverse(PAGES[name])))

@step(r'see a text "(.*)"')
def see_a_text(step, text):
    assert_in(text, world.browser.html)

@step(r'a poll with the title "(.*)"')
def create_a_poll_with_the_title(step, title):
    poll = Poll.objects.create(title=title)
    polls.save()

@step(r'do not see a text "(.*)"')
def do_not_see_a_text(step, text):
    assert_not_in(text, world.browser.html)

Running the tests

Now, you can run python manage.py harvest --test-server to run the tests you just wrote:

$ python manage.py harvest --test-server
Creating test database for alias 'default'...
Django's builtin server is running at 0.0.0.0:9000

Feature: Polls list

  Scenario: Polls list without any polls
    When I access the "polls list" url
    Then I see a text "We didn't find any polls!"

  Scenario: Polls list with one poll
    Given a poll with the title "Hello world"
    When I access the "polls list" url
    Then I see a text "Hello world"
    And I do not see a text "We didn't find any polls!"

1 feature (1 passed)
2 scenarios (2 passed)
6 steps (6 passed)
Destroying test database for alias 'default'...

Don’t forget the --test-server switch - otherwise Lettuce will run tests against your default database.

Sources

You can find some more details on Lettuce and Django integration here: Web development fun with Lettuce and Django.

Update

Rather than using --test-server switch, it’s easier and safer to set a flag in your settings.py (suggested by Michel Sabchuk):

LETTUCE_USE_TEST_DATABASE = True

This way you won’t end up accidentally erasing your production database after forgetting to add --test-server flag.

You can open previous location by hitting Ctrl-O. You can prefix the command with a number to go multiple files back. You can also travel forward in “file history” by using Ctrl-I.

There’s a nice article on Vim Wikia with more details on a subject.