If it won't be simple, it simply won't be. [source code] by Miki Tebeka, CEO, 353Solutions

Thursday, September 28, 2006


I always have trouble remembering which command opens which archive (.tar.gz, .bz2, .zip ...). This is why I have a handy little utility the called "unpack" that knows by the file extension which command to call:

#!/usr/bin/env python
'''Unpack/show compressed files

Create a symbolic link called "zview" to this file or specify -l to view

__author__ = "Miki Tebeka <miki.tebeka@gmail.com>"

from os import system
from os.path import splitext, isfile
from operator import itemgetter

def _stdout(cmd, filename):
return "%s '%s' > '%s'" % (cmd, filename, splitext(filename)[0])

def bz(filename):
return _stdout("bzip2 -d -c", filename)

def gz(filename):
return _stdout("gunzip -c", filename)

class Archive:
extensions = {} # Extension -> instance

def __init__(self, unpack, list, extensions):
self.unpack = unpack
self.list = list
for ext in extensions:
Archive.extensions[ext] = self

Archive("tar -xzvf", "tar -tzf", [".tar.gz", ".tgz", ".tar.z"]),
Archive("tar -xjvf", "tar -tjf", [".tar.bz", ".tar.bz2"]),
Archive(bz, "", [".bz", ".bz2"]),
Archive("tar -xvf", "tar -tf", [".tar"]),
Archive("unzip", "unzip -l", [".zip", "jar", "egg"]),
Archive("unarj e", "unarj l", [".arj"]),
Archive(gz, "", [".gz", ".Z"]),
Archive("unrar x", "unrar lb", [".rar"]),
Archive("7za x", "7za l", [".7z"])

def find_archive(filename):
# Find *longest* matching extension
for ext in sorted(Archive.extensions, reverse=1, key=len):
if filename.lower().endswith(ext):
return Archive.extensions[ext]

def main(argv=None):
if argv is None:
import sys
argv = sys.argv

from optparse import OptionParser
parser = OptionParser(usage="usage: %prog [options] FILE")
parser.add_option("-s", "--show", help="just show command, don't run",
dest="show", action="store_true", default=0)
parser.add_option("-l", "--list", help="list files in archive",
dest="list", action="store_true", default=0)

opts, args = parser.parse_args(argv[1:])
if len(args) != 1:
parser.error("wrong number of arguments") # Will exit

infile = args[0]
if (not opts.show) and (not isfile(infile)):
raise SystemExit("error: can't find %s" % infile)

archive = find_archive(infile)
if not archive:
raise SystemExit("error: don't know how to handle %s" % infile)
list = opts.list or ("zview" in __file__)
command = archive.list if list else archive.unpack

infile = infile.replace("'", "\\'")

if callable(command):
command = command(infile)
command = "%s '%s'" % (command, infile)

if opts.show:
print command
raise SystemExit

raise SystemExit(system(command))

if __name__ == "__main__":

There is also a similar one for viewing zip content.

Thursday, September 14, 2006


cut is a nice littiel utility that cuts a line to several fields and displays only some of them. It's main drawback is that cut splits to fields according to fixed separator, there are times when you want to split accoring to a regular expression - enters ecut.

ecut let's you split fields by regular expression (defaulting to \s+), it also handles Python's array indexing so that you can write someting like:

ls -l | tail +2 | ecut -f -2 | sort -r | head -1

(which is a weird way of finding the last time someting was modified in the current directory :)

ecut code is simple:

#!/usr/bin/env python
'''cut with regular expressions'''

__author__ = "Miki Tebeka "
__license__ = "BSD"
__version__ = "0.2.0"

from sys import stdin
import re
from optparse import OptionParser

progname = "ecut" # Program name
is_range = re.compile("\d+-\d+").match

class FatalError(SystemExit):
def __init__(self, message):
error_message = "%s: error: %s" % (progname, message)
SystemExit.__init__(self, error_message)

parser = OptionParser("usage: %prog [OPTIONS] [FILE]",
version="%%prog %s" % __version__)
default_delimiter = r"\s+"
parser.add_option("-d", "--delimiter", dest="delim",
help="delimiter to use (defaults to '%s')" % default_delimiter)
parser.add_option("-f", "--fields", dest="fields", default=[],
help="comma seperated list of fields to print", metavar="LIST")
parser.add_option("--output-delimiter", dest="out_delim", default=" ",
help="output delimiter", metavar="STRING")
parser.add_option("-s", "--only-delimited", dest="only_delim", default=0,
help="do not print lines not containing delimiters",

opts, args = parser.parse_args()
if not opts.fields:
raise FatalError("no fields given")

# Compile the delimiter
split = re.compile(opts.delim).split
except re.error, e:
raise FatalError("bad regular expression (%s)" % e.args[0])

if not args:
infiles = ["-"]
infiles = args

# Prase fields, we substract 1 since f1 is the 1'st field
fields = []
for field in opts.fields.split(","):
if not is_range(field):
fs = field.split("-")
if len(fs) != 2:
raise ValueError
if fs[0] and fs[1]:
fields.append((int(fs[0]) - 1, int(fs[1]) - 1)) # Full range
elif not fs[0]:
fields.append((0, int(fs[1]) - 1)) # 0-M
else: # M-end
fields.append((int(fs[0]) - 1, -1))
except ValueError:
raise FatalError("bad field: %s" % field)

inttype = type(1) # Ingeter type

# Process input files
for file in infiles:
if file == "-":
info = stdin
info = open(file)
except IOError, e:
raise FatalError("can't open %s - %s" % (file, e.strerror))

for line in info:
out = []
fs = filter(lambda x: x, split(line))
max = len(fs) - 1

if opts.only_delim and (len(fs) == 1):

for field in fields:
if (type(field) == inttype): # Simple field
if field <= max:
else: # Range
start = field[0]
if field[1] == -1:
end = max
end = min(field[2], max)
for i in range(start, end + 1):
print opts.out_delim.join(out)

Thursday, September 07, 2006


Didn't find any good email notification that will support
Gmail for Your Domain so I wrote one.

Very basic, but does exactly what I want. Took me about 2 hours to get the initial version up and running.

Basic design:
  • Store visited emails UIDL in pysqlite database (which will be in 2.5 standard library).
  • Have tray icon change color when there are new messges (I hate popups).
  • Open speficifed web page to view items
  • Small configuration screen
All of this was done using:
Source code is only 226 lines of code.

After that just some py2exe and InnoSetup. We now have a cool looking windows application with an installer.

Blog Archive