<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;A0AGSXYzcSp7ImA9WxdaFkg.&quot;"><id>tag:blogger.com,1999:blog-36613658</id><updated>2008-08-25T03:22:08.889-07:00</updated><title>Sofeng's *Old* Blog</title><subtitle type="html" /><link rel="alternate" type="text/html" href="http://iwiwdsmi.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/36613658/posts/default?start-index=4&amp;max-results=3&amp;redirect=false&amp;v=2" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://iwiwdsmi.blogspot.com/feeds/posts/default" /><author><name>sofeng</name><email>sofengboe@gmail.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>166</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>3</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/Iwiwdsmi" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="iwiwdsmi" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;DE4EQH09cSp7ImA9WxdbEEg.&quot;"><id>tag:blogger.com,1999:blog-36613658.post-5050588810813953513</id><published>2008-07-23T21:13:00.001-07:00</published><updated>2008-08-06T14:08:21.369-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-08-06T14:08:21.369-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="personal" /><title>Transition: Bye Blogger, Bo*ing</title><content type="html">&lt;p&gt;I've come to a transition point in my blogging and professional life. As
  for blogging, I am saying goodbye to Blogger and moving on to my new
  Django blog located at 
  &lt;a href="http://www.saltycrane.com/blog/"&gt;http://www.saltycrane.com/blog/&lt;/a&gt;.
  If everything goes according to plan, this will be my last post here on 
  Blogger.
  My new blog is pretty much finished, so I might as well start using it
  for real. Undoubtedly, I will have to make fixes and tweaks, but that's the
  best way to learn, I suppose. Hopefully, it will serve me as well as Blogger
  has. To be honest, I hope it will serve me better than Blogger (that's why
  I'm moving, right?).
&lt;/p&gt;
&lt;p&gt;Which brings me to my second transition. I am also transitioning out of 
  embedded software and my
  almost ten year career in the Aerospace/Defense industry to a small web
  2.0 startup company. My current company has been great, but kind of
  like my new blog, I am hoping for something even better. Instead of using
  C as my primary language, I will be using Python, Django, Ruby, and Rails.
  It's true I don't have much Ruby or Rails experience, but I plan to learn
  fast. I am currently in Chapter 5 of
  &lt;a href="http://poignantguide.net/ruby/"&gt;Why's (poignant) (and free)
    (and very funny) guide to Ruby&lt;/a&gt;
  and am starting on my newly purchased 
  &lt;a href="http://www.pragprog.com/titles/rails3"&gt;Agile Web Development with
    Rails, 3rd edition Beta&lt;/a&gt;. You can expect I will start posting a lot
  more on Ruby and Rails on my new blog. Hopefully I can add some insightful
  impressions as a devoted Pythonista. (Though, I don't too much care for that
  term.)
&lt;/p&gt;
&lt;p&gt;Well, I'm off! Please &lt;a href="http://www.saltycrane.com/blog/"&gt;come along&lt;/a&gt;!

&lt;p&gt;&lt;em&gt;UPDATE 2008/8/6&lt;/em&gt;: It looks like I get to mostly stick with Python and Django instead of Ruby and Rails. Cool.&lt;/p&gt;</content><link rel="alternate" type="text/html" href="http://iwiwdsmi.blogspot.com/2008/07/transition-bye-blogger-boing.html" title="Transition: Bye Blogger, Bo*ing" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=36613658&amp;postID=5050588810813953513" title="0 Comments" /><link rel="replies" type="application/atom+xml" href="http://iwiwdsmi.blogspot.com/feeds/5050588810813953513/comments/default" title="Post Comments" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/36613658/posts/default/5050588810813953513?v=2" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/36613658/posts/default/5050588810813953513?v=2" /><author><name>sofeng</name><email>sofengboe@gmail.com</email></author></entry><entry gd:etag="W/&quot;C0IHQXc7fSp7ImA9WxdVEU0.&quot;"><id>tag:blogger.com,1999:blog-36613658.post-8342096096846339924</id><published>2008-07-14T22:47:00.000-07:00</published><updated>2008-07-14T22:58:50.905-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-14T22:58:50.905-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="django" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><title>Django Blog Project #9: Migrating Blogger posts with Beautiful Soup</title><content type="html">&lt;p&gt;Last post, I talked about adding comments to my new sample blog application. 
  This was about the last basic feature I needed to add before I started actually
  using it for real. Of course there are still a number of features I'd like to
  add, such as automatic syntax highlighting with Pygments, and incorporating
  django-tagging and some more intersting views, not to mention comment moderation.
  But I think those will have to wait-- I want to start using my new blog for 
  real sometime.&lt;/p&gt;
&lt;p&gt;So for the past few days, I've been working on my 
  &lt;a href="http://www.crummy.com/software/BeautifulSoup/"&gt;Beautiful Soup&lt;/a&gt;
  screen scraper script to copy all my Blogger posts over to my new Django blog.
  Initial results came quickly (it's pretty cool to see such a huge data dump 
  after only a few lines of Beautiful Soup'ing) but the details (especially with
  the comments) kind of slowed 
  me down. I've finally got everything copied over to my satisfaction. Below is
  the script I used to do it. Note, I realize it's not pretty-- just a one time
  use hack. But hopefully someone else doing the same thing might find it useful.
&lt;/p&gt;

&lt;pre style="height: 400px; overflow:auto;"&gt;#!/usr/bin/env python

import datetime
import os
import re
import urllib2
from BeautifulSoup import BeautifulSoup
from myblogapp.models import Post, LegacyComment
from django.contrib.comments.models import FreeComment

URL = ''.join([
        'http://iwiwdsmi.blogspot.com/search?',
        'updated-min=2006-01-01T00%3A00%3A00-08%3A00&amp;'
        'updated-max=2009-01-01T00%3A00%3A00-08%3A00&amp;',
        'max-results=1000'
        ])
html = urllib2.urlopen(URL).read()
soup = BeautifulSoup(html)

for post in soup.html.body.findAll('div', {'class': 'post'}):
    print
    print '--------------------------------------------------------------'

    # save the post title and permalink
    h3 = post.find('h3', {'class': 'post-title'})
    post_href = h3.find('a')['href']
    post_title = h3.find('a').string
    post_slug = os.path.basename(post_href).rstrip('.html')
    print post_slug
    print post_href
    print post_title

    # save the post body
    div = post.find('div', {'class': 'post-body'})
    [toremove.extract() for toremove in div.findAll('script')]
    [toremove.extract() for toremove in div.findAll('span', {'id': 'showlink'})]
    [toremove.extract() for toremove in div.findAll('div', {'style': 'clear: both;'})]
    [toremove.parent.extract() for toremove in div.findAll(text='#fullpost{display:none;}')]
    post_body = ''.join([str(item)
                         for item in div.contents
                         ]).rstrip()
    post_body = re.sub(r"iwiwdsmi\.blogspot\.com/(\d{4}/\d{2}/[\w\-]+)\.html", 
                       r"www.saltycrane.com/blog/\1/", 
                       post_body)

    # count number of highlighted code sections 
    highlight = div.findAll('div', {'class': 'highlight'})
    if highlight:
        hl_count += len(highlight)
        hl_list.append(post_title)

    # save the timestamp
    a = post.find('a', {'class': 'timestamp-link'})
    try:
        post_timestamp = a.string
    except:
        match = re.search(r"\.com/(\d{4})/(\d{2})/", post_href)
        if match:
            year = match.group(1)
            month = match.group(2)
        post_timestamp = "%s/01/%s 11:11:11 AM" % (month, year)
    print post_timestamp

    # save the tags (this is ugly, i know)
    if 'error' in post_title.lower():
        post_tags = ['error']
    else:
        post_tags = []
    span = post.find('span', {'class': 'post-labels'})
    if span:
        a = span.findAll('a', {'rel': 'tag'})
    else:
        a = post.findAll('a', {'rel': 'tag'})
    post_tags = ' '.join([tag.string for tag in a] + post_tags)
    if not post_tags:
        post_tags = 'untagged'
    print post_tags

    # add Post object to new blog
    if True:
        p = Post()
        p.title = post_title
        p.body = post_body
        p.date_created = datetime.datetime.strptime(post_timestamp, "%m/%d/%Y %I:%M:%S %p")
        p.date_modified = p.date_created
        p.tags = post_tags
        p.slug = post_slug
        p.save()

    # check if there are comments
    a = post.find('a', {'class': 'comment-link'})
    if a:
        comm_string = a.string.strip()
    else:
        comm_string = "0"
    if comm_string[0] != "0":
        print
        print "COMMENTS:"

        # get the page with comments
        html_single = urllib2.urlopen(post_href).read()
        soup_single = BeautifulSoup(html_single)

        # get comments
        comments = soup_single.html.body.find('div', {'class': 'comments'})
        cauth_list = comments.findAll('dt')
        cbody_list = comments.findAll('dd', {'class': 'comment-body'})
        cdate_list = comments.findAll('span', {'class': 'comment-timestamp'})

        if not len(cauth_list)==len(cbody_list)==len(cdate_list):
            raise "didn't get all comment data"

        for auth, body, date in zip(cauth_list, cbody_list, cdate_list):
            
            # create comment in database
            lc = LegacyComment()
            lc.body = str(body.p)

            # find author
            lc.author = "Anonymous"
            auth_a = auth.findAll('a')[-1]
            auth_no_a = auth.contents[2]
            if auth_a.string:
                lc.author = auth_a.string
            elif auth_no_a:
                match = re.search(r"\s*([\w\s]*\w)\s+said", str(auth_no_a))
                if match:
                    lc.author = match.group(1)
            print lc.author

            # find website
            try:
                lc.website = auth_a['href']
            except KeyError:
                lc.website = ''
            print lc.website

            # other info
            lc.date_created = datetime.datetime.strptime(
                date.a.string.strip(), "%m/%d/%Y %I:%M %p")
            print lc.date_created
            lc.date_modified = lc.date_created
            lc.post_id = p.id
            lc.save()
&lt;/pre&gt;

&lt;p&gt;I also made some changes to my Django blog code as I migrated my Blogger posts.
  The main addition was a &lt;code&gt;LegacyComment&lt;/code&gt; model along with the associated
  views and templates. My Blogger comments consisted of HTML markup, but I didn't 
  want to allow arbitrary HTML in my new comments for fear of 
  &lt;a href="http://en.wikipedia.org/wiki/Cross-site_scripting"&gt;cross site
    scripting&lt;/a&gt;. So I separated my legacy Blogger comments from my new Django
  site comments.&lt;/p&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;models.py&lt;/b&gt;&lt;br&gt;
&lt;p&gt;Here are my model changes. I added a &lt;code&gt;LegacyComment&lt;/code&gt; class which
  contains pertinent comment attributes and a &lt;code&gt;ForeignKey&lt;/code&gt; to the 
  post that it belongs to. I also added a &lt;code&gt;lc_count&lt;/code&gt; (for legacy
  comment count) field to the &lt;code&gt;Post&lt;/code&gt; class which stores the number
  of comments for the post. It is updated by the &lt;code&gt;save()&lt;/code&gt; method in
  the &lt;code&gt;LegacyComment&lt;/code&gt; class every time a comment is saved. Hmmm,
  I just realized the count will be wrong if I ever edit these comments. Well,
  since these are legacy comments, hopefully I won't have to edit them.
&lt;/p&gt;
&lt;code&gt;~/src/django/myblogsite/myblogapp/models.py&lt;/code&gt;:
&lt;pre style="height:300px; overflow:auto"&gt;import re
from django.db import models

class Post(models.Model):
    title = models.CharField(maxlength=200)
    slug = models.SlugField(maxlength=100)
    date_created = models.DateTimeField() #auto_now_add=True)
    date_modified = models.DateTimeField()
    tags = models.CharField(maxlength=200)
    body = models.TextField()
    body_html = models.TextField(editable=False, blank=True)
    &lt;span style="color:red"&gt;lc_count = models.IntegerField(default=0, editable=False)&lt;/span&gt;

    def get_tag_list(self):
        return re.split(" ", self.tags)

    def get_absolute_url(self):
        return "/blog/%d/%02d/%s/" % (self.date_created.year,
                                      self.date_created.month,
                                      self.slug)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ["-date_created"]

    class Admin:
        pass

&lt;span style="color:red"&gt;class LegacyComment(models.Model):
    author = models.CharField(maxlength=60)
    website = models.URLField(core=False)
    date_created = models.DateTimeField()
    date_modified = models.DateTimeField()
    body = models.TextField()
    post = models.ForeignKey(Post)

    def save(self):
        p = Post.objects.get(id=self.post.id)
        p.lc_count += 1
        p.save()
        super(LegacyComment, self).save()

    class Meta:
        ordering = ["date_created"]

    class Admin:
        pass&lt;/span&gt;
&lt;/pre&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;views.py&lt;/b&gt;&lt;br&gt;
&lt;p&gt;Here is an excerpt from my views.py file showing the changes:&lt;/p&gt;
&lt;code&gt;~/src/django/myblogsite/myblogapp/views.py&lt;/code&gt;:
&lt;pre&gt;import re
from datetime import datetime
from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post, &lt;span style="color:red"&gt;LegacyComment&lt;/span&gt;

MONTH_NAMES = ('', 'January', 'Feburary', 'March', 'April', 'May', 'June', 'July',
               'August', 'September', 'October', 'November', 'December')
MAIN_TITLE = "Sofeng's Blog 0.0.7"

def frontpage(request):
    posts, pagedata = init()
    &lt;span style="color:red"&gt;posts = posts[:5]
    pagedata.update({'post_list': posts,
                     'subtitle': '',})&lt;/span&gt;
    return render_to_response('listpage.html', pagedata)

def singlepost(request, year, month, slug2):
    posts, pagedata = init()
    post = posts.get(date_created__year=year,
                            date_created__month=int(month),
                            slug=slug2,)
    &lt;span style="color:red"&gt;legacy_comments = LegacyComment.objects.filter(post=post.id)
    pagedata.update({'post': post,
                     'lc_list': legacy_comments,})&lt;/span&gt;
    return render_to_response('singlepost.html', pagedata)
&lt;/pre&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;Templates&lt;/b&gt;&lt;br&gt;
&lt;p&gt;In the list page template I used the &lt;code&gt;truncatewords_html&lt;/code&gt; template
  filter to show a 50 word post summary on the list pages instead of the full post.
  I also added the legacy comment count with the Django free comment count to
  display the total number of comments.&lt;/p&gt;
Excerpt from &lt;code&gt;~/src/django/myblogsite/templates/listpage.html&lt;/code&gt;:
&lt;pre&gt;{% block main %}
  &amp;lt;br&amp;gt;
  {% for post in post_list %}
    &amp;lt;h4&amp;gt;&amp;lt;a href="/blog/{{ post.date_created|date:"Y/m" }}/{{ post.slug }}/"&amp;gt;
        {{ post.title }}&amp;lt;/a&amp;gt;
    &amp;lt;/h4&amp;gt;
    {{ post.body&lt;span style="color:red"&gt;|truncatewords_html:"50"&lt;/span&gt; }}
    &lt;span style="color:red"&gt;&amp;lt;a href="{{ post.get_absolute_url }}"&amp;gt;Read more...&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;br&amp;gt;&lt;/span&gt;
    &amp;lt;hr&amp;gt;
    &amp;lt;div class="post_footer"&amp;gt;
      {% ifnotequal post.date_modified.date post.date_created.date %}
        Last modified: {{ post.date_modified.date }}&amp;lt;br&amp;gt;
      {% endifnotequal %}
      Date created: {{ post.date_created.date }}&amp;lt;br&amp;gt;
      Tags: 
      {% for tag in post.get_tag_list %}
        &amp;lt;a href="/blog/tag/{{ tag }}/"&amp;gt;{{ tag }}&amp;lt;/a&amp;gt;{% if not forloop.last %}, {% endif %}
      {% endfor %}
      &amp;lt;br&amp;gt;

      {% get_free_comment_count for myblogapp.post post.id as comment_count %}
      &amp;lt;a href="{{ post.get_absolute_url }}&lt;span style="color:red"&gt;#comments"&amp;gt;
        {{ comment_count|add:post.lc_count }} 
        Comment{{ comment_count|add:post.lc_count|pluralize}}&amp;lt;/a&amp;gt;&lt;/span&gt;

    &amp;lt;/div&amp;gt;
    &amp;lt;br&amp;gt;
  {% endfor %}
{% endblock %}
&lt;/pre&gt;

&lt;p&gt;In the single post template, I added the display of the Legacy comments in addition
  to the Django free comments.&lt;/p&gt;
Excerpt from &lt;code&gt;~/src/django/myblogsite/templates/singlepost.html&lt;/code&gt;:
&lt;pre&gt;  &lt;span style="color:red"&gt;&amp;lt;a name="comments"&amp;gt;&amp;lt;/a&amp;gt;
  {% if lc_list %}
    &amp;lt;h4&amp;gt;{{ lc_list|length }} Legacy Comment{{lc_list|length|pluralize}}&amp;lt;/h4&amp;gt;
  {% endif %}
  {% for legacy_comment in lc_list %}
    &amp;lt;br&amp;gt;
    &amp;lt;a name="lc{{ legacy_comment.id }}" href="#lc{{ legacy_comment.id }}"&amp;gt;
      #{{ forloop.counter }}&amp;lt;/a&amp;gt;
    {% if legacy_comment.website %}
      &amp;lt;a href="{{ legacy_comment.website }}"&amp;gt;
        &amp;lt;b&amp;gt;{{ legacy_comment.author|escape }}&amp;lt;/b&amp;gt;&amp;lt;/a&amp;gt; 
    {% else %}
      &amp;lt;b&amp;gt;{{ legacy_comment.author|escape }}&amp;lt;/b&amp;gt;
    {% endif %}
    commented,
    on {{ legacy_comment.date_created|date:"F j, Y" }} 
    at {{ legacy_comment.date_created|date:"P" }}:
    {{ legacy_comment.body }}
  {% endfor %}
  &amp;lt;br&amp;gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;That's it. Hopefully, I can start using my new blog soon. Please browse around on
  the &lt;a href="http://saltycrane.com/blog"&gt;new Django site&lt;/a&gt; and let me know if you run across any problems. When everything
  looks to be OK, I'll start posting only on my new Django site.&lt;/p&gt;

&lt;p&gt;Here is a snapshot screenshot of version 0.0.8:&lt;/p&gt;
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_lZhqNsiakm4/SHw6-e-2w7I/AAAAAAAAAFo/V9SINeq8LVM/s1600-h/version0.0.8.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp1.blogger.com/_lZhqNsiakm4/SHw6-e-2w7I/AAAAAAAAAFo/V9SINeq8LVM/s320/version0.0.8.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5223114512985932722" /&gt;&lt;/a&gt;

&lt;br&gt;&lt;p&gt;The live site can be viewed at: &lt;a href="http://saltycrane.com/blog"&gt;http://saltycrane.com/blog&lt;/a&gt;
&lt;/p&gt;

&lt;br&gt;Related posts:&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/05/django-new-blog-project.html"&gt;
  Django Blog Project #1: Creating a basic blog&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/06/first-django-webfaction-deployment.html"&gt;
  Django Blog Project #2: Deploying at Webfaction&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/06/django-blog-project-3-using-css-and.html"&gt;
  Django Blog Project #3: Using CSS and Template Inheritance&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/06/django-blog-project-4-adding-post.html"&gt;
  Django Blog Project #4: Adding post metadata&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/06/django-blog-project-5-yui-css-and.html"&gt;
  Django Blog Project #5: YUI CSS and serving static media&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/06/django-blog-project-6-creating-standard.html"&gt;
  Django Blog Project #6: Creating standard blog views&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/06/django-blog-project-7-adding-simple.html"&gt;
  Django Blog Project #7: Adding a simple Atom feed&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;a href="http://iwiwdsmi.blogspot.com/2008/07/django-blog-project8-adding-basic.html"&gt;
  Django Blog Project #8: Adding basic comment functionality&lt;/a&gt;&lt;br&gt;
&lt;br&gt;</content><link rel="alternate" type="text/html" href="http://iwiwdsmi.blogspot.com/2008/07/django-blog-project-9-migrating-blogger.html" title="Django Blog Project #9: Migrating Blogger posts with Beautiful Soup" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=36613658&amp;postID=8342096096846339924" title="2 Comments" /><link rel="replies" type="application/atom+xml" href="http://iwiwdsmi.blogspot.com/feeds/8342096096846339924/comments/default" title="Post Comments" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/36613658/posts/default/8342096096846339924?v=2" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/36613658/posts/default/8342096096846339924?v=2" /><author><name>sofeng</name><email>sofengboe@gmail.com</email></author></entry><entry gd:etag="W/&quot;C0AMSHY5fCp7ImA9WxdWFkg.&quot;"><id>tag:blogger.com,1999:blog-36613658.post-1710193297858998414</id><published>2008-07-09T17:53:00.000-07:00</published><updated>2008-07-09T18:03:09.824-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-07-09T18:03:09.824-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="keyboard" /><title>On the quest for consistent keybindings</title><content type="html">&lt;p&gt;I'm trying to get consistent keybindings in the applications
  which I use the most starting with Emacs, then bash/screen,
  conkeror (my web browser), and others like KPDF and OpenOffice.
  I haven't got complete consistency, but I'm making progress.
  Here are my notes on setting the keybindings for a few of the 
  applications. Maybe when I find the perfect setup, I will write
  about my complete configuration. Until then, here are some sparse
  notes. Note, for some reason, I've mixed in vi-style, h-j-k-l,
  movement keybindings with the standard emacs keybindings. I use
  control+h,j,k,l to move around by character or line and 
  meta+h,j,k,l to move by word or page. I've been using this for 
  several months and it works pretty well-- much better than using 
  pgup/pgdown and the arrow keys.&lt;/p&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;Emacs&lt;/b&gt;&lt;br&gt;
&lt;p&gt;Here is part of my &lt;code&gt;~/.emacs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;;; buffer switching
(iswitchb-mode t)
(global-set-key "\C-b" 'switch-to-buffer)

;; movement key bindings (use h-j-k-l for movement like vi)
(global-set-key "\C-l" 'forward-char)
(global-set-key "\C-h" 'backward-char)
(global-set-key "\C-j" 'next-line)
(global-set-key "\C-k" 'previous-line)
(global-set-key "\M-l" 'forward-word)
(global-set-key "\M-h" 'backward-word)
(global-set-key "\M-j" 'scroll-up)
(global-set-key "\M-k" 'scroll-down)

;; rebind displaced movement key bindings
(global-set-key "\C-p" 'kill-line)
(global-set-key "\M-v" 'downcase-word)
(global-set-key "\M-b" 'recenter)&lt;/pre&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;Readline (bash)&lt;/b&gt;&lt;br&gt;
&lt;p&gt;To change the keybindings for readline (bash), I edited my
  &lt;code&gt;~/.inputrc&lt;/code&gt;. Luckily, readline is very similar
  to Emacs and there are a lot of commands that I didn't know 
  about.  Here are a few examples. For a full
  list of commands see the readline manpage.&lt;/p&gt;
&lt;pre&gt;# ~/.inputrc
"\C-h": backward-char
"\C-l": forward-char
"\M-h": backward-word
"\M-l": forward-word&lt;/pre&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;GNU Screen&lt;/b&gt;&lt;br&gt;
&lt;p&gt;Here are some keybindings I use in GNU Screen.
  I use C-b to switch windows like Emacs buffers. I enter
  copy mode by pressing M-k and exit by pressing C-g.
  The "-m" means the keybinding is for copy mode only.
  "stuff" is used to stuff some stuff into the input buffer.
  "^" is used for Ctrl and "^[" is used for Meta (Alt).&lt;/p&gt;

&lt;pre&gt;# ~/.screenrc
bindkey "^B" select         # like emacs switch buffers
bindkey "^[k" eval "copy" "stuff ^b"
bindkey "^k" eval "copy" "stuff k"
bindkey -m "^[j" stuff ^f   # move down one page
bindkey -m "^[k" stuff ^b   # move up one page
bindkey -m "^[h" stuff b    # move backward word
bindkey -m "^[l" stuff w    # move forward by word
bindkey -m "^j" stuff j     # move down one line
bindkey -m "^k" stuff k     # move up one line
bindkey -m "^h" stuff h     # move back one character
bindkey -m "^l" stuff l     # move forward one character
bindkey -m "^e" stuff $     # move to end of line
bindkey -m "^a" stuff 0     # move to beginning of line
bindkey -m "^f" stuff ^s    # incremental search forward
bindkey -m "^ " stuff " "   # set mark
bindkey -m "^g" stuff q     # exit copy mode&lt;/pre&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;Conkeror&lt;/b&gt;&lt;br&gt;
&lt;p&gt;My &lt;code&gt;~/.conkerorrc&lt;/code&gt; keybindings section is pretty 
  long, so I won't include it. But again, I use C-b to switch 
  buffers, and Ctrl and Meta plus h-j-k-l to navigate.
&lt;/p&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;OpenOffice.org&lt;/b&gt;&lt;br&gt;
&lt;p&gt;This is directly from Appendix A Keyboard Shortcuts in the 
  &lt;a href="http://documentation.openoffice.org/manuals/oooauthors2/0118GS-KeyboardShortcuts.pdf"&gt;
    &lt;em&gt;Getting Started Guide&lt;/em&gt;&lt;/a&gt;. Unfortunately,
  OpenOffice doesn't allow me to use the Meta (Alt) key for
  custom keybindings so I can't use my usual Emacs keybindings.
  Apparently, Peter in &lt;a href="http://user.services.openoffice.org/en/forum/viewtopic.php?f=7&amp;t=4809&amp;p=22203"&gt;this
    forum thread&lt;/a&gt; had the same lament. If anyone knows how to 
  fix this, please let me know.&lt;/p&gt;
&lt;pre&gt;
To adapt shortcut keys to your needs, use the Customize dialog, as described below.
    1) Select Tools &amp;gt; Customize &amp;gt; Keyboard. The Customize dialog (Figure 1) opens.
    2) To have the shortcut key assignment available in all components of OpenOffice.org select the
       OpenOffice.org button.
    3) Next select the required function from the Category and Function lists.
    4) Now select the desired shortcut keys in the Shortcut keys list and click the Modify button at
       the upper right.
    5) Click OK to accept the change. Now the chosen shortcut keys will execute the function
       chosen in step 3 above whenever they are pressed.
&lt;/pre&gt;

&lt;br&gt;&lt;br&gt;&lt;b&gt;KPDF&lt;/b&gt;&lt;br&gt;
&lt;p&gt;I like KPDF better than Evince. I haven't tried anything else.
  To change some keyboard shortcuts in KPDF go to "Settings", 
  "Configure Shortcuts..."&lt;/p&gt;</content><link rel="alternate" type="text/html" href="http://iwiwdsmi.blogspot.com/2008/07/on-quest-for-consistent-keybindings.html" title="On the quest for consistent keybindings" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=36613658&amp;postID=1710193297858998414" title="0 Comments" /><link rel="replies" type="application/atom+xml" href="http://iwiwdsmi.blogspot.com/feeds/1710193297858998414/comments/default" title="Post Comments" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/36613658/posts/default/1710193297858998414?v=2" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/36613658/posts/default/1710193297858998414?v=2" /><author><name>sofeng</name><email>sofengboe@gmail.com</email></author></entry></feed>
