Commits

Miki Tebeka committed cfa8116

Initial import

Comments (0)

Files changed (253)

+Version 0.5.0 [2007/05/31]
+	* Simpler RC file format
+	* Code size reduction
+	* Version
+
+Version 0.4.1 [2007/05/11]
+	* Print alias list sorted
+
+Version 0.4.0 [2007/05/11]
+	* Removed Tk
+
+Version 0.3.0 [2007/01/09]
+	* Code rewrite
+	* When user hit ESC, stay in current directory
+
+Version 0.2.0 [2005/04/12]
+	* Font and color configuration
+	* A lot of code shuffle
+	* Added environment variables expansion
+	* Change email to gmail
+	* Added "jt" alias
+	* Versioning changed to MAJOR.MINOR.PATCH
+
+Version 0.1 [2005/01/02]
+	* First public revision
+# Makefile for "bcd" project
+ 
+# =====================================================
+# Copyright (c) Miki Tebeka <miki.tebeka@gmail.com> 
+# This file is under the GNU Public License (GPL), see
+# http://www.gnu.org/copyleft/gpl.html for more details
+# =====================================================
+
+# Miki Tebeka <miki.tebeka@gmail.com>
+
+# $Id: Makefile 17 2007-05-11 21:26:54Z tebeka $
+
+# You'll need the following tools:
+# Python - http://www.python.org
+# py2exe - http://starship.python.net/crew/theller/py2exe
+#
+# Just type "bin\make" on a DOS console (or "make" on cygwin shell)
+ 
+ 
+DIST = bcd
+ZIP = bcd.zip
+WZIP = bcdw32.zip
+SRCZIP = bcdsrc.zip
+
+SOURCES = Makefile \
+		  bcd.bat \
+		  bcd.py \
+		  bcd.sh \
+		  pyexe.py \
+		  se \
+		  setup.py
+
+DOCS = bcdrc_example \
+	   README.txt
+
+ifeq ($(OSTYPE),cygwin)
+	PYTHON = ${shell cygpath -au `cmd /c pyexe.py`}
+endif
+
+ifeq ($(OSTYPE),win32)
+	PWD = ${shell cmd /c cd}
+	PATH := $(PWD)\bin;$(PATH)
+	PYTHON = $(shell cmd /c pyexe.py)
+endif
+
+ifeq ($(OSTYPE),linux-gnu)
+	python = ${shell python pyexe.py}
+endif
+
+all:
+	@echo Try either \"dist\" or \"w32dist\" targets
+	@echo
+
+initial:
+	rm -fr $(DIST)
+	mkdir $(DIST)
+	cp $(DOCS) $(DIST)
+
+dist: $(ZIP)
+
+$(ZIP): initial bcd.py bcd.sh $(DOCS)
+	cp bcd.py $(DIST)
+	cp bcd.sh $(DIST)
+	zip -r9 $(ZIP) $(DIST)
+
+w32dist: $(WZIP)
+
+$(WZIP): initial bcd.py bcd.bat setup.py $(DOCS)
+	$(PYTHON) setup.py py2exe -d $(DIST)
+	mv $(DIST)/bcd.exe $(DIST)/bcdw.exe
+	cp bcd.bat $(DIST)
+	zip -r9 $(WZIP) $(DIST)
+
+srcdist: $(SRCZIP)
+
+$(SRCZIP): initial $(SOURCES)
+	cp -r $(SOURCES) $(DIST)
+	rm -fr $(DIST)/bin/.svn
+	zip -r9 $(SRCZIP) $(DIST)
+
+clean:
+	rm -fr $(DIST)
+	rm -fr build
+	rm -fr $(ZIP) $(WZIP) $(SRCZIP)
+
+.PHONY: all dist w32dist clean srcdist initial
+==========================
+"bcd" version 0.5.0 README
+==========================
+:Author: Miki Tebeka <miki.tebeka@gmail.com>
+:Data: $Date: 2008-02-15 13:38:12 -0800 (Fri, 15 Feb 2008) $
+
+.. contents::
+
+
+.. Note::
+    I no longer maintain BCD since I moved to linux and found
+    `bash-completion`_.
+
+    If you feel the project is useful and would like to take over, drop me_ a
+    line.
+
+What is "bcd" 
+=============
+"bcd" stand for "Better CD". It is a "cd" replacement in the spirit of "cdargs"
+(http://www.skamphausen.de/software/cdargs/).
+
+"bcd" allows you to give symbolic names (aliases) to directories and then "cd"
+to these directories.
+
+Installation
+============
+Just unzip the zip file to a directory and place it in your path.
+Unix users will want to add the line "source /path/to/bcd/.bcd.sh" to their
+.bashrc
+
+Running
+=======
+Just run "bcd" [alias]. If there is the alias is matched exactly you'll change
+directory. Otherwise all the aliases will be printed to screen.
+
+On Unix systems hitting "TAB" after bcd will complete all aliases (at least on
+bash).
+
+Configuration File
+==================
+The configuration file has the following syntax:
+
+::
+
+    work = $HOME/work
+    download = $HOME/downloads
+    cool_project = /mnt/cool_project
+
+See the bcdrc_example for more details
+
+Environment Variables
+=====================
+BCDRC is the location of the configuration file (otherwise it's $HOME/.bcd).
+
+Downloading etc
+===============
+See http://developer.berlios.de/projects/bcd/ 
+
+ToDo
+====
+* Find someone to take over the project :)
+* Add path completion - `bcd project1/l<TAB>` will complete all
+  directories under /path/to/project1/l*
+
+.. _`bash-completion`: http://www.caliban.org/bash/
+.. _me: mailto:miki.tebeka@gmail.com
+
+
+.. comment: vim:ft=rst spell
+@echo off
+
+rem Batch file to call bcd.py
+
+rem =====================================================
+rem Copyright (c) Miki Tebeka <miki.tebeka@gmail.com> 
+rem This file is under the GNU Public License (GPL), see
+rem http://www.gnu.org/copyleft/gpl.html for more details
+rem =====================================================
+
+rem Miki Tebeka <miki.tebeka@gmail.com>
+rem $Id: bcd.bat 5 2005-04-12 10:46:05Z tebeka $
+
+if "%1" == "-h" goto HELP
+
+set tmp_script=%TEMP%\bcd_tmp.bat
+bcdw.exe -w %1 > %tmp_script%
+
+rem Since we don't use "call" any line after the next *won't* be called
+%tmp_script%
+
+:HELP
+bcdw -h
+#!/usr/bin/env python
+'''Tkinter replacement to cdargs utility'''
+
+# =====================================================
+# Copyright (c) Miki Tebeka <miki.tebeka@gmail.com> 
+# This file is under the GNU Public License (GPL), see
+# http://www.gnu.org/copyleft/gpl.html for more details
+# =====================================================
+
+__author__ = "Miki Tebeka <miki.tebeka@gmail.com>"
+__version__ = "0.5.0"
+
+from user import home
+from os.path import join, isfile, expandvars, isdir
+from os import environ, system
+from sys import platform, stderr
+from ConfigParser import ConfigParser, Error as ConfigParserError
+
+ALIASES = []
+
+def rc_filename():
+    if "BCDRC" in environ:
+        return environ["BCDRC"]
+
+    if platform == "win32":
+        prefix = "_"
+    else:
+        prefix = "."
+    return join(home, prefix + "bcdrc")
+
+def load_rc(filename):
+    aliases = {}
+    for line in open(filename):
+        line = line.strip()
+        if (not line) or line[0] == "#":
+            continue
+        name, path = line.split("=", 1)
+        name = name.strip()
+        path = path.strip()
+
+        aliases[name] = path
+
+    return aliases
+
+def print_path(path):
+    path = expandvars(path)
+    if platform == "win32":
+        print "@echo off"
+        print "cd /d %s" % path
+    else:
+        print path
+
+def check(aliases):
+    ok = 1
+    items = aliases.items()
+    items.sort()
+    for alias, path in items:
+        print "%s: %s" % (alias, path),
+        path = resolve(path)
+        if not isdir(path):
+            print "[ERROR]"
+            ok = 0
+        else:
+            print "[OK]"
+
+    return ok
+
+def main(argv = None):
+
+    if argv is None:
+        import sys
+        argv = sys.argv
+
+    from optparse import OptionParser
+
+    # Command line parsing
+    parser = OptionParser("usage: %prog [options] [ALIAS]", 
+            version="bcd " + __version__)
+    parser.add_option("-c", help="compelte alias", dest="complete", default=0,
+            action="store_true")
+    parser.add_option("--check", help="check aliases", dest="check", default=0,
+            action="store_true")
+
+    opts, args = parser.parse_args()
+    if (not opts.complete) and (not opts.check) and \
+        (len(args) not in (0, 1)):
+        parser.error("wrong number of arguments") # Will exit
+
+    rcfile = rc_filename()
+
+    if not isfile(rcfile):
+        raise SystemExit("can't find initialization file %s" % rcfile)
+
+    try:
+        # Initial load of rc file
+        aliases = load_rc(rcfile)
+    except (IOError, ConfigParserError, ValueError), e:
+        raise SystemExit("bcd: %s: error: %s" % (rcfile, e))
+
+    if opts.check:
+        retval = 0
+        if not check(aliases):
+            retval = 1
+        raise SystemExit(retval)
+
+    # Print all aliases starting with argument
+    if opts.complete:
+        if args:
+            prefix = args[1]
+        else:
+            prefix = "" # string.startswith("") is always true
+        for alias in aliases:
+            if alias.startswith(prefix):
+                print alias
+        raise SystemExit
+
+    # Try to find given alias
+    if args:
+        path = aliases.get(args[0], None)
+        if path:
+            print_path(path)
+            raise SystemExit
+
+    # Not found or no arguments, show all options
+    # We print to stderr so it won't be caught but the wrapping function
+    max_alias_length = max([len(a[0]) for a in aliases])
+    padding = 5
+    for alias, path in sorted(aliases.items()):
+        line = "-" * (max_alias_length + padding - len(alias))
+        print >> stderr, "%s %s %s" % (alias, line, path)
+
+    if args: # Didn't find
+        raise SystemExit("error: can't find %s" % args[0])
+
+if __name__ == "__main__":
+    main()
+# bash utility functions for bcd
+# Source this in your .bashrc
+
+# =====================================================
+# Copyright (c) Miki Tebeka <miki.tebeka@gmail.com> 
+# This file is under the GNU Public License (GPL), see
+# http://www.gnu.org/copyleft/gpl.html for more details
+# =====================================================
+
+# $Id: bcd.sh 20 2007-05-31 19:07:12Z tebeka $
+
+# BCD function, call the bcd utility and change directory
+bcd()
+{
+    output=`bcd.py $@`
+    if [ -z "$output" ]; then
+        return;
+    elif [ -d "$output" ]; then
+        cd "$output"
+    else
+        echo "$output"
+    fi
+}
+
+# Also "Jump To"
+alias jt=bcd
+
+# Set completion
+complete -C "bcd.py -c" bcd
+complete -C "bcd.py -c" jt

bcd/bcdrc_example

+# A .bcdrc example
+# Copy to your home directory under the name .bcdrc (or _bcdrc on windows) and
+# edit to fit your settings
+
+# =====================================================
+# Copyright (c) Miki Tebeka <miki.tebeka@gmail.com> 
+# This file is under the GNU Public License (GPL), see
+# http://www.gnu.org/copyleft/gpl.html for more details
+# =====================================================
+
+# Miki Tebeka <miki.tebeka@gmail.com>
+
+home = /home/duffy
+work = /home/duffy/work
+project = /home/duffy/work/hello-world

bcd/bin/make.exe

Binary file added.

bcd/bin/mkdir.exe

Binary file added.

bcd/bin/mv.exe

Binary file added.

bcd/bin/rm.exe

Binary file added.

bcd/bin/zip.exe

Binary file added.
+'''Just print out the location of the python executable'''
+
+__author__ = "Miki Tebeka <miki.tebeka@gmail.com>"
+# $Id: //DVD-R/ADP/i60/dev/tools/pyexe.py#2 $
+
+from sys import executable
+print executable
+# Set environmet for debuggin
+# internal tool only
+
+source bcd.sh
+export PATH="$PATH:$HOME/bcd"
+from distutils.core import setup
+import py2exe
+
+setup(console=["bcd.py"])

crashlog/LICENSE.txt

+Copyright (c) 2007 Miki Tebeka <miki.tebeka@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(If you don't know, this is MIT license :)

crashlog/README.txt

+=================
+`crashlog` README
+=================
+:Date: $Date: 2007-03-07 10:47:56 -0800 (Wed, 07 Mar 2007) $
+:Author: Miki Tebeka <miki.tebeka@gmail.com>
+
+What?
+=====
+`carshlog` is a simple utility the reports crashes to your application via email
+and log file
+
+License
+-------
+See here_
+
+How?
+====
+Either use `import crashlog` at the top of your application or run 
+`python -m crashlog`.
+
+Configuration
+-------------
+Currently you'll need to change the default values in `crashlog.py`:
+
+* `_LOG_FILE`
+* `_MAIL_TO`
+* All the section below `if gethostname() != "production-machine":`
+
+Where?
+======
+https://developer.berlios.de/projects/crashlog/
+
+Who?
+====
+Just me_ currently, however if you have the time and energy ...
+
+
+.. _me: mailto:miki.tebeka@gmail.com
+.. _here: LICENSE.txt
+
+
+.. comment: vim:ft=rst spell

crashlog/crashlog.py

+'''Log creashes to file and mail them.
+
+Use either "import crashlog" in your code or run with "python -m crashlog"
+'''
+
+__author__ = "Miki Tebeka <miki.tebeka@gmail.com>"
+
+# This program is distributed under the MIT license, please read
+# http://www.opensource.org/licenses/mit-license.php
+
+import sys
+from cStringIO import StringIO
+from datetime import datetime
+from email.MIMEText import MIMEText
+from smtplib import SMTP
+from traceback import print_tb
+from os import environ
+from os.path import split, isdir, join
+from socket import gethostname
+
+_LOG_FILE = "/tmp/crashlog.log"
+_MAIL_TO = [
+    "taz@looney.com",
+    "duffy@looney.com",
+    "bugs@looney.com"
+]
+
+# Don't mail anyone when not on production machine, just the developer
+if gethostname() != "production-machine":
+    default_user = "duffy"
+    if "USER" in environ:
+        user = environ["USER"]
+    elif "SCRIPT_FILENAME" in environ:
+        dirs = split(environ["SCRIPT_FILENAME"])
+        # /home/duffy
+        if isdir(join(*dirs[:2])):
+            user = dirs[1]
+        else:
+            user = default_user
+    else:
+        user = default_user
+
+    _MAIL_TO = [ "%s@looney.com" % user ]
+
+_LOADED = 0
+_AS_MAIN = 0
+
+def format_message(type, value, traceback):
+    # execfile playes tricks with argv
+    if _AS_MAIN:
+        program = sys.argv[1]
+        args = sys.argv[1:]
+    else:
+        program = sys.argv[0]
+        args = sys.argv
+
+    # Format message
+    io = StringIO()
+    print >> io, "Arguments: %s" % " ".join(args)
+    print >> io, "Date: %s" % datetime.now()
+    print >> io, "Environment:"
+    for var in environ:
+        print >> io, "\t%s --> %s" % (var, environ[var])
+    
+    print >> io, "Traceback:"
+    print_tb(traceback, file=io)
+    print >> io, "%s: %s" % (type, value)
+    print >> io
+
+    return io.getvalue(), program
+
+def write_to_log(message):
+    fo = open(_LOG_FILE, "at")
+    fo.write(message)
+    fo.close()
+
+def mail_message(message, program):
+    mail = MIMEText(message)
+    mail["From"] = "crashlog@%s" % gethostname()
+    mail["To"] = "Crashers"
+    mail["Subject"] = "%s crashed" % program
+    smtp = SMTP("smtp.looney.com")
+
+    smtp.helo()
+    smtp.starttls()
+    smtp.sendmail("crashlog@looney.com", _MAIL_TO, mail.as_string())
+    smtp.close()
+
+def excepthook(type, value, traceback):
+    message, program = format_message(type, value, traceback)
+    write_to_log(message)
+    mail_message(message, program)
+
+    # Run original exception hook
+    sys.__excepthook__(type, value, traceback)
+
+if not _LOADED:
+    sys.excepthook = excepthook
+    _LOADED = 1
+
+if __name__ == "__main__":
+    _AS_MAIN = 1
+    execfile(sys.argv[1])

crashlog/default.css

+/*
+:Author: David Goodger
+:Contact: goodger@users.sourceforge.net
+:date: $Date$
+:version: $Revision$
+:copyright: This stylesheet has been placed in the public domain.
+
+Default cascading style sheet for the HTML output of Docutils.
+*/
+
+div.document .first {
+  margin-top: 0 }
+
+div.document .last {
+  margin-bottom: 0 }
+
+div.document a.toc-backref {
+  text-decoration: none ;
+  color: black }
+
+div.document dd {
+  margin-bottom: 0.5em }
+
+div.document div.abstract {
+  margin: 2em 5em }
+
+div.document div.abstract p.topic-title {
+  font-weight: bold ;
+  text-align: center }
+
+div.document div.attention,
+div.document div.caution,
+div.document div.danger,
+div.document div.error,
+div.document div.hint,
+div.document div.important,
+div.document div.note,
+div.document div.tip,
+div.document div.warning,
+div.document div.admonition {
+  margin: 2em ;
+  border: medium outset ;
+  padding: 1em }
+
+div.document div.attention p.admonition-title,
+div.document div.caution p.admonition-title,
+div.document div.danger p.admonition-title,
+div.document div.error p.admonition-title,
+div.document div.warning p.admonition-title {
+  color: red ;
+  font-weight: bold ;
+  font-family: sans-serif }
+
+div.document div.hint p.admonition-title,
+div.document div.important p.admonition-title,
+div.document div.note p.admonition-title,
+div.document div.tip p.admonition-title,
+div.document div.admonition p.admonition-title {
+  font-weight: bold ;
+  font-family: sans-serif }
+
+div.document div.dedication {
+  margin: 2em 5em ;
+  text-align: center ;
+  font-style: italic }
+
+div.document div.dedication p.topic-title {
+  font-weight: bold ;
+  font-style: normal }
+
+div.document div.figure {
+  margin-left: 2em }
+
+div.document div.footer,
+div.document div.header {
+  font-size: smaller }
+
+div.document div.sidebar {
+  margin-left: 1em ;
+  border: medium outset ;
+  padding: 0em 1em ;
+  background-color: #ffffee ;
+  width: 40% ;
+  float: right ;
+  clear: right }
+
+div.document div.sidebar p.rubric {
+  font-family: sans-serif ;
+  font-size: medium }
+
+div.document div.system-messages {
+  margin: 5em }
+
+div.document div.system-messages h1 {
+  color: red }
+
+div.document div.system-message {
+  border: medium outset ;
+  padding: 1em }
+
+div.document div.system-message p.system-message-title {
+  color: red ;
+  font-weight: bold }
+
+div.document div.topic {
+  margin: 2em }
+
+div.document h1.title {
+  text-align: center }
+
+div.document h2.subtitle {
+  text-align: center }
+
+div.document hr {
+  width: 75% }
+
+div.document ol.simple, ul.simple {
+  margin-bottom: 1em }
+
+div.document ol.arabic {
+  list-style: decimal }
+
+div.document ol.loweralpha {
+  list-style: lower-alpha }
+
+div.document ol.upperalpha {
+  list-style: upper-alpha }
+
+div.document ol.lowerroman {
+  list-style: lower-roman }
+
+div.document ol.upperroman {
+  list-style: upper-roman }
+
+div.document p.attribution {
+  text-align: right ;
+  margin-left: 50% }
+
+div.document p.caption {
+  font-style: italic }
+
+div.document p.credits {
+  font-style: italic ;
+  font-size: smaller }
+
+div.document p.label {
+  white-space: nowrap }
+
+div.document p.rubric {
+  font-weight: bold ;
+  font-size: larger ;
+  color: darkred ;
+  text-align: center }
+
+div.document p.sidebar-title {
+  font-family: sans-serif ;
+  font-weight: bold ;
+  font-size: larger }
+
+div.document p.sidebar-subtitle {
+  font-family: sans-serif ;
+  font-weight: bold }
+
+div.document p.topic-title {
+  font-weight: bold }
+
+div.document pre.address {
+  margin-bottom: 0 ;
+  margin-top: 0 ;
+  font-family: serif ;
+  font-size: 100% }
+
+div.document pre.line-block {
+  font-family: serif ;
+  font-size: 100% }
+
+div.document pre.literal-block, pre.doctest-block {
+  margin-left: 2em ;
+  margin-right: 2em ;
+  background-color: #eeeeee }
+
+div.document span.classifier {
+  font-family: sans-serif ;
+  font-style: oblique }
+
+div.document span.classifier-delimiter {
+  font-family: sans-serif ;
+  font-weight: bold }
+
+div.document span.interpreted {
+  font-family: sans-serif }
+
+div.document span.option {
+  white-space: nowrap }
+
+div.document span.option-argument {
+  font-style: italic }
+
+div.document span.pre {
+  white-space: pre }
+
+div.document span.problematic {
+  color: red }
+
+div.document table {
+  margin-top: 0.5em ;
+  margin-bottom: 0.5em }
+
+div.document table.citation {
+  border-left: solid thin gray ;
+  padding-left: 0.5ex }
+
+div.document table.docinfo {
+  margin: 2em 4em }
+
+div.document table.footnote {
+  border-left: solid thin black ;
+  padding-left: 0.5ex }
+
+div.document td,
+div.document th {
+  padding-left: 0.5em ;
+  padding-right: 0.5em ;
+  vertical-align: top }
+
+div.document th.docinfo-name,
+div.document th.field-name {
+  font-weight: bold ;
+  text-align: left ;
+  white-space: nowrap }
+
+div.document h1 tt,
+div.document h2 tt,
+div.document h3 tt,
+div.document h4 tt,
+div.document h5 tt,
+div.document h6 tt {
+  font-size: 100% }
+
+div.document tt {
+  background-color: #eeeeee }
+
+div.document ul.auto-toc {
+  list-style-type: none }

crashlog/make-html

+#!/bin/bash
+
+rst2html.py --stylesheet=style.css README.txt README.html

crashlog/style.css

+/*
+:Author: Fred L. Drake, Jr.
+:date: $Date$
+:version: $Revision$
+
+This stylesheet combines some ideas from the two stylesheets
+distributed with docutils and enhances them for Zope 3 documentation.
+*/
+
+@import url(default.css);
+
+div.document {
+  margin: 0px 1em 1em 4em;
+  padding: 0px; }
+
+div.document a {
+  text-decoration: none; }
+
+div.document a[href] {
+  text-decoration: underline; }
+
+div.document h1.title {
+  background-image: url("zope3logo.gif");
+  background-position: -6px -4px;
+  background-repeat: no-repeat;
+  font-size: 150%;
+  min-height: 50px; }
+
+div.document div.section {
+  margin: 0px 0px 1.5em 0px; }
+
+div.document div.section h1 {
+  background-color: rgb(230,230,230);
+  margin-left: -2em;
+  padding: 0.2em;
+  padding-left: 0.35em;
+  padding-top: 0.35em;
+  /* This grey underline make this more visually distinctive on LCD
+     monitors, which often don't have enough contrast. */
+  border-right: thin solid rgb(180,180,180);
+  border-bottom: thin solid rgb(180,180,180); }
+
+div.document div.section div.section div.section h3 {
+  margin-bottom: -0.5em; }
+
+div.document h1 {
+  font-family: sans-serif;
+  font-size: 135%; }
+
+div.document h2 {
+  font-family: sans-serif;
+  font-size: 120%; }
+
+div.document h3 {
+  font-family: sans-serif;
+  font-size: 105%; }
+
+div.document h4 {
+  font-family: sans-serif;
+  font-size: 100%; }
+
+div.document h5 {
+  font-family: sans-serif;
+  font-size: 100%; }
+
+div.document h6 {
+  font-family: sans-serif;
+  font-style: italic;
+  font-size: 100%; }
+
+div.document hr {
+  width: 75%; }
+
+div.document .literal .pre {
+  background-color: white;
+  font-family: lucidatypewriter, "lucida typewriter", sans-serif; }
+
+div.document .literal-block {
+  border: thin solid rgb(180,180,180);
+  font-family: lucidatypewriter, "lucida typewriter", monospace;
+  font-size: 80%;
+  padding: 0.5em; }
+
+div.document table.table {
+  margin-left: 2em;
+  margin-right: 2em; }
+
+div.document table.table thead {
+  background-color: rgb(230,230,230); }
+
+/* docutils uses the "option" class with both "col" and "span"
+   elements, so we have to be explicit here */
+div.document .option-list span.option {
+  font-weight: bold; }
+
+div.document .option-list kbd {
+  font-family: inherit; }
+/* Minimal unit testing for C
+   Version 0.2
+
+Usage:
+- Write you test function in the format void test_func();
+- Sprinkle c_unit_assert(value, msg) in your test functions
+- Add desired tests using c_unit_add(test, name)
+- Run the tests using c_unit_run()
+
+Example:
+    #include "c-unit.h"
+
+    void
+    test_ok()
+    {
+        c_unit_assert(1, "this one is OK");
+    }
+
+    void
+    test_fail()
+    {
+        c_unit_assert(0, "this one fails");
+    }
+
+    int
+    main()
+    {
+        c_unit_add(test_ok, "test_ok");
+        c_unit_add(test_fail, "test_fail");
+
+        return c_unit_run() != 1;
+    }
+
+It is also recommened to use the following scheme in your test files:
+    #include "c-unit.h"
+    #define TESTING
+    #include "t.c"
+
+    <test code goes here>
+
+This way you'll be able to access static functions and variables as well.
+If you have a "main" in t.c then enclose it in #ifdef TESTING
+*/
+
+/*
+=====================================================
+Copyright (c) Miki Tebeka <miki.tebeka@google.com> 
+This file is under the GNU Public License (GPL), see
+http://www.gnu.org/copyleft/gpl.html for more details
+=====================================================
+
+$Id: c-unit.h 972 2004-11-07 06:42:51Z mikit $
+*/
+#ifndef C_UNIT_H
+#define C_UNIT_H
+
+#include <stdio.h>
+#include <setjmp.h>
+#include <stdarg.h>
+
+/* We don't need #ifdef __cplusplus since all the code is in the header file
+*/
+
+static int __c_unit_curr_test; /**< Current test */
+static jmp_buf __c_unit_err; /**< longjmp target */
+#define __C_UNIT_MAX_TESTS 100 /**< Maximal number of tests */
+typedef void (*__c_unit_test_func)(); /**< Test function type */
+
+/** Test structure */
+typedef struct {
+	__c_unit_test_func test; /**< Test function */
+	char *name; /**< Test name */
+} __c_unit_test_t;
+
+__c_unit_test_t __c_unit_tests[__C_UNIT_MAX_TESTS]; /**< Array of tests */
+static int __c_unit_num_tests = 0; /**< Number of tests */
+
+/** Add a test to the test suite
+  @param test Test function
+  @param name Test name
+*/
+static void
+c_unit_add(__c_unit_test_func test, char *name)
+{
+	__c_unit_tests[__c_unit_num_tests].test = test;
+	__c_unit_tests[__c_unit_num_tests].name = name;
+	++__c_unit_num_tests;
+}
+
+/** Assert a test value
+  @param value Assert value 
+  @param format "printf" format
+  @param ... Rest of arguments
+
+  Modifies __c_unit_ok
+*/
+static void
+c_unit_assert(int value, char *format, ...)
+{
+	char *name; /* Name of test */
+    va_list argptr; /* Argument pointer */
+
+    if (value) {
+        return;
+    }
+	
+    /* Test name */
+    name = __c_unit_tests[__c_unit_curr_test].name;
+    printf("%s: error: ", name);
+    va_start(argptr, format);
+    vprintf(format, argptr);
+    va_end(argptr);
+
+    printf("\n");
+    fflush(stdout);
+
+    longjmp(__c_unit_err, 1);
+}
+
+/** Run test suite. Print progress and summary
+  @return 0 if any test failed, 1 otherwise
+
+  Modifies __c_unit_ok, __c_unit_curr_test
+*/
+static int
+c_unit_run()
+{
+	int success, fail;
+
+	success = fail = 0;
+
+	printf("Running %d tests...\n", __c_unit_num_tests);
+	for (__c_unit_curr_test = 0; 
+		 __c_unit_curr_test < __c_unit_num_tests;
+		 ++__c_unit_curr_test) {
+		printf("* %s:\n", __c_unit_tests[__c_unit_curr_test].name);
+        if (setjmp(__c_unit_err)) {
+            ++fail;
+			printf("Test FAILED\n\n");
+            continue;
+        }
+		__c_unit_tests[__c_unit_curr_test].test();
+        ++success;
+        printf("Test OK\n\n");
+	}
+
+	printf("Total of %d tests passed, %d failed\n", success, fail);
+
+	return (fail == 0);
+}
+
+#endif /* C_UNIT_H */

gmailtray/ChangeLog

+Version 0.2.1
+	* Lock around checking status
+	* gc.collect() gets called on timer
+	* Added "dist" option to Makefile
+	* Fixed typo in README
+	* Update URLs in README
+
+Version 0.2.0
+	* Text based configuration file
+	* First public release to BerliOS
+
+Version 0.1.0
+	* Initial version

gmailtray/LICENSE.txt

+Copyright (c) 2006, Miki Tebeka <miki.tebeka@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Miki Tebeka nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

gmailtray/Makefile

+VERSION = $(shell cat VERSION)
+PYTHON = $(shell pyexe.py)
+INNO = $(shell innoexe.py)
+BASENAME = gmailtray_$(VERSION)
+INSTALLER_BASE = $(BASENAME)_setup
+INSTALLER = $(INSTALLER_BASE).exe 
+EXTRA = README.html *.ico *.css LICENSE.txt ChangeLog
+ARCHDIR = $(subst _,-,$(BASENAME))
+ARCHIVE = $(ARCHDIR).tar.bz2
+SRCFILES = $(shell $(PYTHON) srcfiles.py)
+
+all:
+	@echo "Done :) (you probably want to run 'make installer')"
+
+installer: $(INSTALLER)
+
+$(INSTALLER): dist setup.iss version.iss
+	$(INNO) setup.iss
+
+dist: gmailtray.py setup.py version.py $(EXTRA)
+	$(PYTHON) setup.py py2exe
+	cp -f $(EXTRA) $@
+
+README.html: README.txt
+	sed -e "s/_VERSION_/$(VERSION)/" $< | rst2html.py --stylesheet=style.css > $@
+
+srcdist: $(ARCHIVE)
+
+$(ARCHIVE): $(SRCFILES)
+	mkdir $(ARCHDIR)
+	cp $(SRCFILES) $(ARCHDIR)
+	tar -cjf $(ARCHIVE) $(ARCHDIR)
+	rm -fr $(ARCHDIR)
+
+clean:
+	rm -f version.py version.iss
+	rm -fr dist build
+	rm -f *.pyc
+	rm -f README.html
+	rm -f $(INSTALLER)
+
+version.iss: VERSION
+	echo "AppVerName = GmailTray version $(VERSION)" > $@
+	echo "OutputBaseFileName = $(INSTALLER_BASE)" >> $@
+
+version.py: VERSION
+	echo "VERSION = \"$(VERSION)\"" > $@
+
+fresh: clean all
+
+.PHONY: all installer clean fresh

gmailtray/README.txt

+==============
+GmailTray Help
+==============
+:Author: Miki Tebeka <miki.tebeka@gmail.com>
+:Version: _VERSION_
+
+What?
+=====
+GmailTray is a *very simple* Gmail traybar notification program.
+
+Why?
+====
+Because I needed one that will support "Gmail For Your Domain". And it took me
+less that 2 hours to get the first version working.
+
+Where?
+======
+HomePage:
+    http://gmailtray.berlios.de/
+
+Downloads:
+    http://developer.berlios.de/project/showfiles.php?group_id=7262
+
+How?
+====
+Python_ + wxPython_.
+
+When?
+=====
+See the ChangeLog_
+
+.. _Python: http://www.python.org
+.. _wxPython: http://www.wxpython.org
+.. _ChangeLog: ChangeLog
+
+.. comment: vim:ft=rst spell

gmailtray/VERSION

+0.2.1

gmailtray/default.css

+/*
+:Author: David Goodger
+:Contact: goodger@users.sourceforge.net
+:date: $Date$
+:version: $Revision$
+:copyright: This stylesheet has been placed in the public domain.
+
+Default cascading style sheet for the HTML output of Docutils.
+*/
+
+div.document .first {
+  margin-top: 0 }
+
+div.document .last {
+  margin-bottom: 0 }
+
+div.document a.toc-backref {
+  text-decoration: none ;
+  color: black }
+
+div.document dd {
+  margin-bottom: 0.5em }
+
+div.document div.abstract {
+  margin: 2em 5em }
+
+div.document div.abstract p.topic-title {
+  font-weight: bold ;
+  text-align: center }
+
+div.document div.attention,
+div.document div.caution,
+div.document div.danger,
+div.document div.error,
+div.document div.hint,
+div.document div.important,
+div.document div.note,
+div.document div.tip,
+div.document div.warning,
+div.document div.admonition {
+  margin: 2em ;
+  border: medium outset ;
+  padding: 1em }
+
+div.document div.attention p.admonition-title,
+div.document div.caution p.admonition-title,
+div.document div.danger p.admonition-title,
+div.document div.error p.admonition-title,
+div.document div.warning p.admonition-title {
+  color: red ;
+  font-weight: bold ;
+  font-family: sans-serif }
+
+div.document div.hint p.admonition-title,
+div.document div.important p.admonition-title,
+div.document div.note p.admonition-title,
+div.document div.tip p.admonition-title,
+div.document div.admonition p.admonition-title {
+  font-weight: bold ;
+  font-family: sans-serif }
+
+div.document div.dedication {
+  margin: 2em 5em ;
+  text-align: center ;
+  font-style: italic }
+
+div.document div.dedication p.topic-title {
+  font-weight: bold ;
+  font-style: normal }
+
+div.document div.figure {
+  margin-left: 2em }
+
+div.document div.footer,
+div.document div.header {
+  font-size: smaller }
+
+div.document div.sidebar {
+  margin-left: 1em ;
+  border: medium outset ;
+  padding: 0em 1em ;
+  background-color: #ffffee ;
+  width: 40% ;
+  float: right ;
+  clear: right }
+
+div.document div.sidebar p.rubric {
+  font-family: sans-serif ;
+  font-size: medium }
+
+div.document div.system-messages {
+  margin: 5em }
+
+div.document div.system-messages h1 {
+  color: red }
+
+div.document div.system-message {
+  border: medium outset ;
+  padding: 1em }
+
+div.document div.system-message p.system-message-title {
+  color: red ;
+  font-weight: bold }
+
+div.document div.topic {
+  margin: 2em }
+
+div.document h1.title {
+  text-align: center }
+
+div.document h2.subtitle {
+  text-align: center }
+
+div.document hr {
+  width: 75% }
+
+div.document ol.simple, ul.simple {
+  margin-bottom: 1em }
+
+div.document ol.arabic {
+  list-style: decimal }
+
+div.document ol.loweralpha {
+  list-style: lower-alpha }
+
+div.document ol.upperalpha {
+  list-style: upper-alpha }
+
+div.document ol.lowerroman {
+  list-style: lower-roman }
+
+div.document ol.upperroman {
+  list-style: upper-roman }
+
+div.document p.attribution {
+  text-align: right ;
+  margin-left: 50% }
+
+div.document p.caption {
+  font-style: italic }
+
+div.document p.credits {
+  font-style: italic ;
+  font-size: smaller }
+
+div.document p.label {
+  white-space: nowrap }
+
+div.document p.rubric {
+  font-weight: bold ;
+  font-size: larger ;
+  color: darkred ;
+  text-align: center }
+
+div.document p.sidebar-title {
+  font-family: sans-serif ;
+  font-weight: bold ;
+  font-size: larger }
+
+div.document p.sidebar-subtitle {
+  font-family: sans-serif ;
+  font-weight: bold }
+
+div.document p.topic-title {
+  font-weight: bold }
+
+div.document pre.address {
+  margin-bottom: 0 ;
+  margin-top: 0 ;
+  font-family: serif ;
+  font-size: 100% }
+
+div.document pre.line-block {
+  font-family: serif ;
+  font-size: 100% }
+
+div.document pre.literal-block, pre.doctest-block {
+  margin-left: 2em ;
+  margin-right: 2em ;
+  background-color: #eeeeee }
+
+div.document span.classifier {
+  font-family: sans-serif ;
+  font-style: oblique }
+
+div.document span.classifier-delimiter {
+  font-family: sans-serif ;
+  font-weight: bold }
+
+div.document span.interpreted {
+  font-family: sans-serif }
+
+div.document span.option {
+  white-space: nowrap }
+
+div.document span.option-argument {
+  font-style: italic }
+
+div.document span.pre {
+  white-space: pre }
+
+div.document span.problematic {
+  color: red }
+
+div.document table {
+  margin-top: 0.5em ;
+  margin-bottom: 0.5em }
+
+div.document table.citation {
+  border-left: solid thin gray ;
+  padding-left: 0.5ex }
+
+div.document table.docinfo {
+  margin: 2em 4em }
+
+div.document table.footnote {
+  border-left: solid thin black ;
+  padding-left: 0.5ex }
+
+div.document td,
+div.document th {
+  padding-left: 0.5em ;
+  padding-right: 0.5em ;
+  vertical-align: top }
+
+div.document th.docinfo-name,
+div.document th.field-name {
+  font-weight: bold ;
+  text-align: left ;
+  white-space: nowrap }
+
+div.document h1 tt,
+div.document h2 tt,
+div.document h3 tt,
+div.document h4 tt,
+div.document h5 tt,
+div.document h6 tt {
+  font-size: 100% }
+
+div.document tt {
+  background-color: #eeeeee }
+
+div.document ul.auto-toc {
+  list-style-type: none }

gmailtray/dotdict.py

+# Subclass of "dict", where items can be access by d["key"] or d.key
+# (Simplified version of http://feedparser.org)
+
+# Miki Tebeka <miki.tebeka@gmail.com>
+
+class DotDict(dict):
+	def __getattr__(self, attr):
+		try:
+			return self.__dict__[attr]
+		except KeyError:
+			pass
+		try:
+			return self[attr]
+		except KeyError:
+			raise AttributeError(
+                "'%s' object has no attribute '%s'" % \
+                    (self.__class__.__name__, attr))
+
+	def __setattr__(self, attr, value):
+		return self.__setitem__(attr, value)
+

gmailtray/error.ico

Added
New image

gmailtray/gmailtray.py

+# Yet another gmail tray icon notifier
+# Miki Tebeka <miki.tebeka@gmail.com>
+
+from sys import path
+from os.path import isfile, dirname, join
+from poplib import POP3_SSL
+from itertools import count
+import webbrowser
+import wx
+from dotdict import DotDict
+from threading import Lock
+import gc
+try:
+    from version import VERSION
+except ImportError:
+    VERSION = "???"
+
+# We keep UIDL of message in sqlite DB
+try: # Python 2.5+ version
+    from sqlite3 import connect, Error as DBError
+except ImportError: # Older Python version
+    from pysqlite2.dbapi2 import connect, Error as DBError
+
+# Application directory
+APPDIR = path[0]
+if isfile(APPDIR): # py2exe
+    APPDIR = dirname(APPDIR)
+
+# Icons to use
+ICON_NO_MAIL = "no_mail.ico"
+ICON_NEW_MAIL = "new_mail.ico"
+ICON_ERROR = "error.ico"
+
+# No email tooltip
+TIP_NO_MAIL = "No new mail"
+
+DB_FILE = join(APPDIR, "messages.db")
+DB = None
+
+# Configuration file is Python syntax
+# FIXME: Encrypt password
+CONFIG_FILE = join(APPDIR, "gmailtray.ini")
+DEFAULT_CONFIG =  DotDict({
+    "login" : "",
+    "password" : "",
+    "web_page" : "http://gmail.google.com",
+})
+
+def load_config():
+    if not isfile(CONFIG_FILE):
+        return DEFAULT_CONFIG
+
+    config = DotDict()
+    execfile(CONFIG_FILE, globals(), config)
+    return config
+
+def save_config(config):
+    fo = open(CONFIG_FILE, "wt")
+    for key, value in config.iteritems():
+        if isinstance(value, basestring):
+            value = "r\"%s\"" % value
+        print >> fo, "%s = %s" % (key, value)
+    fo.close()
+
+def run_config():
+    config = load_config()
+    dlg = ConfigDlg(config)
+    if dlg.ShowModal() == wx.ID_OK:
+        config = dlg.get_config()
+        try:
+            save_config(config)
+        except DBError:
+            wx.LogError("Error saving configuration")
+    dlg.Destroy()
+
+def initialize():
+    cur = cursor()
+    try:
+        cur.execute("create table messages (id string)")
+    except DBError:
+        # FIXME: Should we ignore this?
+        pass
+    cur.connection.commit()
+    cur.close()
+    run_config()
+
+def cursor():
+    return DB.cursor()
+
+def is_message_new(message_id):
+    cur = cursor()
+    try:
+        cur.execute("select count(*) from messages where id = ?", 
+                (message_id, ))
+        count, = cur.fetchone()
+        return count == 0
+    finally:
+        cur.close()
+
+def get_message_id_list(user, password):
+    pop = POP3_SSL("pop.gmail.com")
+    message_id_list = []
+    try:
+        pop.user(user)
+        pop.pass_(password)
+
+        num_messages, size = pop.stat()
+        for id in count(1):
+            if id > num_messages:
+                break
+            id_line = pop.uidl(id)
+            message_id_list.append(id_line.split()[-1])
+
+        return message_id_list
+    finally:
+        pop.quit()
+
+def save_messages(message_id_list):
+    cur = cursor()
+    try:
+        for id in message_id_list:
+            cur.execute("insert into messages (id) values (?)", (id, ))
+    finally:
+        cur.close()
+        cur.connection.commit()
+
+def get_new_message_ids(config):
+    new_message_id_list = []
+    for message_id in get_message_id_list(config.login, config.password):
+        if is_message_new(message_id):
+            new_message_id_list.append(message_id)
+
+    return new_message_id_list
+
+def load_icon(name):
+    iconfile = join(APPDIR, name)
+    if not isfile(iconfile):
+        raise IOError
+
+    return wx.Icon(iconfile, wx.BITMAP_TYPE_ICO)
+
+# Email:    ______________
+# Password: ______________
+# --------------
+# [Save] [Quit]
+class ConfigDlg(wx.Dialog):
+    def __init__(self, config):
+        wx.Dialog.__init__(self, None, -1, "GmailTray Configuration")
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        # Email:    ______________
+        # Password: ______________
+        gsizer = wx.FlexGridSizer(4, 2) # Rows, cols
+        def add(name, value, style=0):
+            gsizer.Add(wx.StaticText(self, -1, "%s:" % name), 0,
+                wx.ALIGN_CENTER_VERTICAL)
+            text = wx.TextCtrl(self, -1, value=value, size=(300, -1),
+                    style=style)
+            gsizer.Add(text, 0, wx.EXPAND)
+            return text
+
+        self._login = add("Email", config["login"])
+        self._password = add("Password", config["password"], wx.TE_PASSWORD)
+        self._url = add("Web Page:", config["web_page"])
+
+        sizer.Add(gsizer, 1, wx.EXPAND|wx.WEST, 2)
+
+        # --------------
+        sl = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL, size=(-1, 2))
+        sizer.Add(sl, 0, wx.EXPAND|wx.NORTH|wx.SOUTH|wx.EAST|wx.WEST, 5)
+
+        # [Save] [Quit]
+        hsizer = wx.BoxSizer(wx.HORIZONTAL)
+        hsizer.Add(wx.Button(self, wx.ID_OK))
+        hsizer.Add(wx.Button(self, wx.ID_CANCEL))
+        sizer.Add(hsizer, 0, wx.EXPAND)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+    def get_config(self):
+        def _get(control):
+            return control.GetValue().strip()
+
+        return {
+            "login" : _get(self._login),
+            "password" : _get(self._password),
+            "web_page" : _get(self._url),
+        }
+
+class GmailTray(wx.TaskBarIcon):
+    TBMENU_REFRESH = wx.NewId()
+    TBMENU_CLOSE = wx.NewId()
+    TBMENU_VIEW = wx.NewId()
+    TBMENU_CONFIG = wx.NewId()
+
+    def __init__(self):
+        wx.TaskBarIcon.__init__(self)
+        self.set_icon(ICON_ERROR, "Connecting ...")
+        self.num_new_messages = 0
+        self.lock = Lock()
+
+        self.Bind(wx.EVT_TIMER, self.OnTimer)
+        self.timer = wx.Timer(self) 
+        # wx.Timer gets milliseconds, we want 5 minutes
+        self.timer.Start(1000 * 60 * 5)
+        self.check_status()
+
+        def handle_menu(id, handler):
+            self.Bind(wx.EVT_MENU, handler, id=id)
+        handle_menu(self.TBMENU_REFRESH, self.OnRefresh)
+        handle_menu(self.TBMENU_CLOSE, self.OnClose)
+        handle_menu(self.TBMENU_VIEW, self.OnView)
+        handle_menu(self.TBMENU_CONFIG, self.OnConfig)
+        self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnView)
+        self.Bind(wx.EVT_CLOSE, self.OnClose)
+
+    def check_status(self):
+        # Non blocking lock
+        if not self.lock.acquire(0):
+            return
+
+        try:
+            new_message_ids = get_new_message_ids(load_config())
+            if new_message_ids:
+                save_messages(new_message_ids)
+                self.num_new_messages += len(new_message_ids)
+                icon = ICON_NEW_MAIL
+                tooltip = "%d new email(s)" % self.num_new_messages
+            else:
+                icon = ICON_NO_MAIL
+                tooltip = TIP_NO_MAIL
+        except Exception: # FIXME: Catch more specific errors
+            icon = ICON_ERROR
+            tooltip = "Connection error"
+        finally:
+            self.lock.release()
+
+        self.set_icon(icon, tooltip)
+
+    def OnRefresh(self, evt):
+        self.check_status()
+
+    def OnClose(self, evt):
+        self.RemoveIcon()
+        raise SystemExit
+
+    def OnView(self, evt):
+        self.num_new_messages = 0
+        self.set_icon(ICON_NO_MAIL, TIP_NO_MAIL)
+        webbrowser.open("http://gmail.google.com")
+
+    def CreatePopupMenu(self):
+        menu = wx.Menu()
+        menu.Append(self.TBMENU_REFRESH, "Check now")
+        menu.Append(self.TBMENU_VIEW, "View web page")
+        menu.Append(self.TBMENU_CONFIG, "Configure ...")
+        menu.AppendSeparator()
+        menu.Append(self.TBMENU_CLOSE, "Exit")
+
+        return menu
+
+    def OnTimer(self, evt):
+        self.check_status()
+        gc.collect()
+
+    def OnConfig(self, evt):
+        run_config()
+
+    def set_icon(self, icon_file, message):
+        icon = load_icon(icon_file)
+        tooltip = "[GmailTray] %s" % message
+        self.SetIcon(icon, tooltip)
+
+def main(argv=None):
+    global DB
+
+    if argv is None:
+        import sys
+        argv = sys.argv
+
+    from optparse import OptionParser
+    parser = OptionParser("usage: %prog [options]",
+            version="GmailTray version %s" % VERSION)
+    parser.add_option("--initialize", dest="initialize", default=0,
+        action="store_true")
+
+    opts, args = parser.parse_args(argv[1:])
+    if args:
+        parser.error("wrong number of arguments") # Will exit
+
+    # This must be first
+    app = wx.PySimpleApp()
+    DB = connect(DB_FILE)
+
+    if opts.initialize:
+        try:
+            initialize()
+        except Exception, e:
+            wx.LogError("%s" % e)
+            raise SystemExit
+        raise SystemExit
+
+    gm = GmailTray()
+    app.MainLoop()
+
+if __name__ == "__main__":
+    main()

gmailtray/innoexe.py

+#!/usr/bin/env python
+'''Find InnoSetup executable'''
+
+# Miki Tebeka <miki.tebeka@gmail.com>
+
+from sys import platform
+
+UNINST = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
+ISCC = "ISCC.exe"
+INNO = "Inno Setup"
+NOT_FOUND = "error: can't find Inno Setup registery key"
+LOCK_KEY = "InstallLocation"
+
+if platform == "win32": 
+    from _winreg import OpenKey, QueryValueEx, EnumKey, CloseKey, \
+            HKEY_LOCAL_MACHINE 
+    from itertools import count
+    from os.path import join
+
+
+    key = OpenKey(HKEY_LOCAL_MACHINE, UNINST)
+    next = count(0).next
+
+    # Find unistall key
+    while 1:
+        try:
+            keyname = EnumKey(key, next())
+            if INNO in keyname:
+                break
+        except WindowsError: # Passed last one
+            raise SystemExit(NOT_FOUND)
+
+    CloseKey(key)
+    key = OpenKey(HKEY_LOCAL_MACHINE, UNINST + "\\" + keyname)
+    # Get install directory
+    value, type = QueryValueEx(key, LOCK_KEY)
+    CloseKey(key)
+
+    # Print full path to compiler
+    print join(value, ISCC)
+
+    raise SystemExit
+
+# Cygwin starts here
+from os import popen
+UNINST = UNINST.replace("\\", "/")
+for line in popen("/bin/regtool list /HKLM/%s" % UNINST):
+    if INNO in line:
+        break
+else:
+    raise SystemExit(NOT_FOUND)
+keyname = line.strip()
+path = popen("/bin/regtool get '/HKLM/%s/%s/%s'" % \
+        (UNINST, keyname, LOCK_KEY)).read().strip()
+path += ISCC
+print popen("/bin/cygpath -au '%s'" % path).read().strip()

gmailtray/new_mail.ico