From 26e33a55215635ed21d81ac83b22b53397380486 Mon Sep 17 00:00:00 2001 From: Andrew Flegg Date: Tue, 20 Oct 2009 21:04:20 +0100 Subject: [PATCH] Big update to 0.1.0. Improved error handling, syncing, the works... --- package/debian/changelog | 14 +++ package/src/contacts.py | 20 +++- package/src/gui.py | 92 ++++++++++----- package/src/hermes.py | 99 ++++++++++++----- package/src/names.py | 4 +- package/src/trans.py | 278 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 446 insertions(+), 61 deletions(-) create mode 100644 package/src/trans.py diff --git a/package/debian/changelog b/package/debian/changelog index a3cef84..1720051 100644 --- a/package/debian/changelog +++ b/package/debian/changelog @@ -1,3 +1,17 @@ +hermes (0.1.0) unstable; urgency=low + + * Improve error handling, including opening accounts dialogue + for failed Twitter auth MB#5352 (reported by Zach Goldberg) + * Use Python `trans' module to match differing accents (reported + by Valério Valério) + * Add ability to create "birthday-only" contacts from Facebook + (suggested by Jussi Mäkinen) + * Pull down homepages from Facebook, and extract URLs. + * Clarify accounts dialogue & Facebook authentication (suggested + by Keith Varty) + + -- Andrew Flegg Sun, 18 Oct 2009 09:36:36 +0100 + hermes (0.0.6) unstable; urgency=low * New icons from Tim Samoff. diff --git a/package/src/contacts.py b/package/src/contacts.py index 4963191..38b4624 100644 --- a/package/src/contacts.py +++ b/package/src/contacts.py @@ -5,6 +5,7 @@ import Image import ImageOps import StringIO import datetime +import re from pygobject import * from ctypes import * @@ -103,10 +104,21 @@ class ContactStore: # ----------------------------------------------------------------------- - def add_url(self, contact, url, unique = ''): + def add_url(self, contact, str, unique = ''): """Add a new URL to the set of URLs for the given contact.""" - unique = unique or url + urls = re.findall('(?:(?:ftp|https?):\/\/|\\bwww\.|\\bftp\.)[,\w\.\-\/@:%?&=%+#~_$\*]+[\w=\/&=+#]', str, re.I | re.S) + updated = False + for url in urls: + updated = self._add_url(contact, url, unique or url) or updated + + return updated + + + # ----------------------------------------------------------------------- + def _add_url(self, contact, url, unique): + """Do the work of adding a unique URL to a contact.""" + url_attr = None ai = GList.new(ebook.e_contact_get_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL)) while ai.has_next(): @@ -117,13 +129,13 @@ class ContactStore: elif existing.find(unique) > -1: url_attr = attr break - + if not url_attr: ai.add() url_attr = EVCardAttribute() url_attr.group = '' url_attr.name = 'URL' - + val = GList() print "Setting URL for [%s] to [%s]" % (contact.get_name(), url) val.set(create_string_buffer(url)) diff --git a/package/src/gui.py b/package/src/gui.py index b82a71c..c283ff1 100755 --- a/package/src/gui.py +++ b/package/src/gui.py @@ -40,7 +40,8 @@ class HermesGUI: try: fb2c = Hermes(self, twitter = (self.get_use_twitter() and self.get_twitter_credentials()) or None, - facebook = self.get_use_facebook()) + facebook = self.get_use_facebook(), + empty = self.get_create_empty()) fb2c.load_friends() fb2c.sync_contacts(resync = force) gobject.idle_add(self.open_summary, fb2c) @@ -114,47 +115,81 @@ class HermesGUI: dialog = gtk.Dialog('Accounts', self.window) dialog.add_button('Save', gtk.RESPONSE_OK) - use_facebook = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT) - use_facebook.set_label('Use Facebook') + #pa = hildon.PannableArea() + #dialog.vbox.add(pa) + content = dialog.vbox + #content = gtk.VBox() + #pa.add(content) + #pa.set_size_request(600, 380) + + use_facebook = self.new_checkbox('Use Facebook', content) use_facebook.set_active(self.get_use_facebook()) - dialog.vbox.add(use_facebook) - use_twitter = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT) - use_twitter.set_label('Use Twitter') + indent = self.new_indent(content) + fb_msg = self.add_linked(indent, use_facebook, gtk.Label('Note: authentication via web page')) + fb_msg.set_property('justify', gtk.JUSTIFY_LEFT) + + fb_empty = self.add_linked(indent, use_facebook, self.new_checkbox('Create birthday-only contacts')) + fb_empty.set_active(self.get_create_empty()) + + use_twitter = self.new_checkbox('Use Twitter', content) use_twitter.set_active(self.get_use_twitter()) - dialog.vbox.add(use_twitter) - tw_indent = gtk.HBox() - tw_user = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT) - tw_user.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL) - tw_user.set_placeholder("Twitter username") - tw_user.set_property('is-focus', False) + indent = self.new_indent(content) + tw_user = self.add_linked(indent, use_twitter, self.new_input('Twitter username')) tw_user.set_text(self.get_twitter_credentials()[0]) - self.sync_edit(use_twitter, tw_user) - use_twitter.connect('toggled', self.sync_edit, tw_user) - tw_indent.pack_start(tw_user, padding=48) - dialog.vbox.add(tw_indent) - - tw_indent = gtk.HBox() - tw_pass = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT) - tw_pass.set_placeholder("Twitter password") + + tw_pass = self.add_linked(indent, use_twitter, self.new_input('Twitter password')) tw_pass.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE) tw_pass.set_text(self.get_twitter_credentials()[1]) - self.sync_edit(use_twitter, tw_pass) - use_twitter.connect('toggled', self.sync_edit, tw_pass) - tw_indent.pack_start(tw_pass, padding=48) - dialog.vbox.add(tw_indent) dialog.show_all() result = dialog.run() dialog.hide() if result == gtk.RESPONSE_OK: self.set_use_facebook(use_facebook.get_active()) + self.set_create_empty(fb_empty.get_active()) self.set_use_twitter(use_twitter.get_active(), tw_user.get_text(), tw_pass.get_text()) return result - + + # ----------------------------------------------------------------------- + def new_checkbox(self, label, box = None): + checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT) + checkbox.set_label(label) + if box: + box.add(checkbox) + return checkbox + + + # ----------------------------------------------------------------------- + def new_indent(self, box): + outer = gtk.HBox() + indent = gtk.VBox() + outer.pack_start(indent, padding=48) + box.add(outer) + return indent + + + # ----------------------------------------------------------------------- + def new_input(self, text, box = None): + input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT) + input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL) + input.set_placeholder(text) + input.set_property('is-focus', False) + if box: + box.add(input) + return input + + + # ----------------------------------------------------------------------- + def add_linked(self, box, ctrl, to_add): + box.add(to_add) + self.sync_edit(ctrl, to_add) + ctrl.connect('toggled', self.sync_edit, to_add) + return to_add + # ----------------------------------------------------------------------- def sync_edit(self, use_twitter, edit): @@ -278,6 +313,13 @@ class HermesGUI: def set_use_facebook(self, value): self.gc.set_bool("/apps/maemo/hermes/use_facebook", value) + def get_create_empty(self): + return self.gc.get_bool("/apps/maemo/hermes/create_empty") + + + def set_create_empty(self, value): + self.gc.set_bool("/apps/maemo/hermes/create_empty", value) + def get_use_twitter(self): return self.gc.get_bool("/apps/maemo/hermes/use_twitter") diff --git a/package/src/hermes.py b/package/src/hermes.py index ffed489..e7ec2fc 100644 --- a/package/src/hermes.py +++ b/package/src/hermes.py @@ -2,7 +2,7 @@ import os.path import evolution from facebook import Facebook, FacebookError import twitter -import unicodedata +import trans import gnome.gconf from contacts import ContactStore import names @@ -26,7 +26,7 @@ class Hermes: # ----------------------------------------------------------------------- - def __init__(self, callback, twitter = None, facebook = False): + def __init__(self, callback, twitter = None, facebook = False, empty = False): """Constructor. Passed a callback which must implement three informational methods: @@ -45,12 +45,16 @@ class Hermes: facebook - boolean indicating if Facebook should be used. Defaults to False. + + empty - boolean indicating if 'empty' contacts consisting of a profile + URL and birthday should be created. """ self.gc = gnome.gconf.client_get_default() self.callback = callback self.twitter = twitter self.facebook = facebook + self.create_empty = empty # -- Check the environment is going to work... # @@ -117,11 +121,14 @@ class Hermes: self.do_fb_login() # Get the list of friends... - attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url'] + attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website'] for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs): - friend['name'] = unicodedata.normalize('NFKD', unicode(friend['name'])) + key = unicode(friend['name']).encode('trans') + self.friends[key] = friend friend['pic'] = friend[attrs[2]] - self.friends[friend['name']] = friend + if friend['website']: + friend['homepage'] = friend['website'] + if not friend['pic']: self.blocked_pictures.append(friend) @@ -132,7 +139,7 @@ class Hermes: api = twitter.Api(username=user, password=passwd) users = api.GetFriends() for friend in api.GetFriends(): - self.friends[friend.name] = {'name': unicodedata.normalize('NFKD', unicode(friend.name)), 'pic': friend.profile_image_url, 'birthday_date': None, 'twitter_url': 'http://twitter.com/%s' % (friend.screen_name), 'homepage' : friend.url} + self.friends[friend.name] = {'name': unicode(friend.name).encode('trans'), 'pic': friend.profile_image_url, 'birthday_date': None, 'twitter_url': 'http://twitter.com/%s' % (friend.screen_name), 'homepage' : friend.url} # TODO What if the user has *no* contacts? @@ -147,7 +154,7 @@ class Hermes: print "+++ Syncing contacts..." addresses = evolution.ebook.open_addressbook('default') print "+++ Addressbook opened..." - store = ContactStore(addresses) + self.store = ContactStore(addresses) print "+++ Contact store created..." self.updated = [] self.unmatched = [] @@ -162,29 +169,9 @@ class Hermes: found = False for name in names.variants(contact.get_name()): if name in self.friends: - friend = self.friends[name] + updated = self.update_contact(contact, self.friends[name], resync) found = True - updated = False - - if friend['pic'] and (resync or contact.get_property('photo') is None): - updated = store.set_photo(contact, friend['pic']) or updated - - if friend['birthday_date'] and (resync or contact.get_property('birth-date') is None): - date_str = friend['birthday_date'].split('/') - date_str.append('0') - updated = store.set_birthday(contact, int(date_str[1]), - int(date_str[0]), - int(date_str[2])) or updated - - if 'profile_url' in friend and friend['profile_url']: - updated = store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated - - if 'twitter_url' in friend and friend['twitter_url']: - updated = store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated - - if 'homepage' in friend and friend['homepage']: - updated = store.add_url(contact, friend['homepage']) or updated - + if updated: self.updated.append(contact) addresses.commit_contact(contact) @@ -197,5 +184,57 @@ class Hermes: else: self.unmatched.append(contact) - store.close() + # -- Create 'empty' contacts with birthdays... + # + if self.create_empty: + for name in self.friends: + friend = self.friends[name] + if 'contact' in friend or 'birthday_date' not in friend or not friend['birthday_date']: + continue + + contact = evolution.ebook.EContact() + contact.props.full_name = friend['name'] + contact.props.given_name = friend['first_name'] + contact.props.family_name = friend['last_name'] + + self.update_contact(contact, friend) + + addresses.add_contact(contact) + self.updated.append(contact) + addresses.commit_contact(contact) + + print "Created [%s]" % (contact.get_name()) + self.matched.append(contact) + + self.store.close() + + + # ----------------------------------------------------------------------- + def update_contact(self, contact, friend, resync = False): + """Update the given contact with information from the 'friend' + dictionary.""" + + updated = False + friend['contact'] = contact + + if friend['pic'] and (resync or contact.get_property('photo') is None): + updated = self.store.set_photo(contact, friend['pic']) or updated + + if friend['birthday_date'] and (resync or contact.get_property('birth-date') is None): + date_str = friend['birthday_date'].split('/') + date_str.append('0') + updated = self.store.set_birthday(contact, int(date_str[1]), + int(date_str[0]), + int(date_str[2])) or updated + + if 'profile_url' in friend and friend['profile_url']: + updated = self.store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated + + if 'twitter_url' in friend and friend['twitter_url']: + updated = self.store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated + + if 'homepage' in friend and friend['homepage']: + updated = self.store.add_url(contact, friend['homepage']) or updated + + return updated diff --git a/package/src/names.py b/package/src/names.py index d560841..0835e8e 100644 --- a/package/src/names.py +++ b/package/src/names.py @@ -1,4 +1,4 @@ -import unicodedata +import trans """Utilities for determing name variants. @@ -34,7 +34,7 @@ def variants(name): if (name is None): return result - name = unicodedata.normalize('NFKD', unicode(name)) + name = unicode(name).encode('trans') result.add(name) bits = name.split(' ') for bit in bits: diff --git a/package/src/trans.py b/package/src/trans.py new file mode 100644 index 0000000..e130142 --- /dev/null +++ b/package/src/trans.py @@ -0,0 +1,278 @@ +# coding: utf-8 + +ur""" + +The **trans** module +==================== + +This module translates national characters into similar sounding +latin characters (transliteration). +At the moment, Greek, Turkish, Russian, Ukrainian, Czech, Polish, +Latvian alphabets are supported (it covers 99% of needs). + +.. contents:: + +Simple usage +------------ +It's very easy to use +~~~~~~~~~~~~~~~~~~~~~ + >>> # coding: utf-8 + >>> import trans + >>> u'Hello World!'.encode('trans') + u'Hello World!' + >>> u'Привет, Мир!'.encode('trans') + u'Privet, Mir!' + + +Work only with unicode strings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + >>> 'Hello World!'.encode('trans') + Traceback (most recent call last): + ... + TypeError: trans codec support only unicode string, given. + +This is readability +~~~~~~~~~~~~~~~~~~~ + >>> s = u'''\ + ... -- Раскудрить твою через коромысло в бога душу мать + ... триста тысяч раз едрену вошь тебе в крыло + ... и кактус в глотку! -- взревел разъяренный Никодим. + ... -- Аминь, -- робко добавил из склепа папа Пий. + ... (c) Г. Л. Олди, "Сказки дедушки вампира".''' + >>> + >>> print s.encode('trans') + -- Raskudrit tvoyu cherez koromyslo v boga dushu mat + trista tysyach raz edrenu vosh tebe v krylo + i kaktus v glotku! -- vzrevel razyarennyy Nikodim. + -- Amin, -- robko dobavil iz sklepa papa Piy. + (c) G. L. Oldi, "Skazki dedushki vampira". + +Table "**id**" +~~~~~~~~~~~~~~ +Use the table "id", leaving only the Latin characters, digits and underscores: + + >>> print u'1 2 3 4 5 \n6 7 8 9 0'.encode('trans') + 1 2 3 4 5 + 6 7 8 9 0 + >>> print u'1 2 3 4 5 \n6 7 8 9 0'.encode('trans/id') + 1_2_3_4_5__6_7_8_9_0 + >>> s.encode('trans/id')[-42:-1] + u'_c__G__L__Oldi___Skazki_dedushki_vampira_' + + +Define user tables +------------------ +Simple variant +~~~~~~~~~~~~~~ + >>> u'1 2 3 4 5 6 7 8 9 0'.encode('trans/my') + Traceback (most recent call last): + ... + ValueError: Table "my" not found in tables! + >>> trans.tables['my'] = {u'1': u'A', u'2': u'B'}; + >>> u'1 2 3 4 5 6 7 8 9 0'.encode('trans/my') + u'A_B________________' + >>> + +A little harder +~~~~~~~~~~~~~~~ +Table can consist of two parts - the map of diphthongs and map of characters. +First are processed diphthongs, by simple replacement on the substring. +Then according to the map of characters, replacing each character of string +by it's mapping. If character is absent in characters map, checked key None, +if not, then is used the default character u'_'. + + >>> diphthongs = {u'11': u'AA', u'22': u'BB'} + >>> characters = {u'a': u'z', u'b': u'y', u'c': u'x', + ... u'A': u'A', u'B': u'B', None: u'-'} + >>> trans.tables['test'] = (diphthongs, characters) + >>> u'11abc22cbaCC'.encode('trans/test') + u'AAzyxBBxyz--' + +**The characters created by processing of diphthongs are also processed +by the map of the symbols:** + + >>> diphthongs = {u'11': u'AA', u'22': u'BB'} + >>> characters = {u'a': u'z', u'b': u'y', u'c': u'x', None: u'-'} + >>> trans.tables['test'] = (diphthongs, characters) + >>> u'11abc22cbaCC'.encode('trans/test') + u'--zyx--xyz--' + +Without the diphthongs +~~~~~~~~~~~~~~~~~~~~~~ +These two tables are equivalent: + + >>> characters = {u'a': u'z', u'b': u'y', u'c': u'x', None: u'-'} + >>> trans.tables['t1'] = characters + >>> trans.tables['t2'] = ({}, characters) + >>> u'11abc22cbaCC'.encode('trans/t1') == u'11abc22cbaCC'.encode('trans/t2') + True + + +Finally +------- ++ *Special thanks to Yuri Yurevich aka j2a for the kick in the right direction.* + - http://www.python.su/forum/viewtopic.php?pid=28965 + - http://code.djangoproject.com/browser/django/trunk/django/contrib/admin/media/js/urlify.js ++ *I please forgiveness for my bad English. I promise to be corrected.* + +""" + +__version__ = '1.1a7' +__author__ = 'Zelenyak Aleksandr aka ZZZ ' + +latin = { + u'à': u'a', u'á': u'a', u'â': u'a', u'ã': u'a', u'ä': u'a', u'å': u'a', + u'æ': u'ae', u'ç': u'c', u'è': u'e', u'é': u'e', u'ê': u'e', u'ë': u'e', + u'ì': u'i', u'í': u'i', u'î': u'i', u'ï': u'i', u'ð': u'd', u'ñ': u'n', + u'ò': u'o', u'ó': u'o', u'ô': u'o', u'õ': u'o', u'ö': u'o', u'ő': u'o', + u'ø': u'o', u'ù': u'u', u'ú': u'u', u'û': u'u', u'ü': u'u', u'ű': u'u', + u'ý': u'y', u'þ': u'th', u'ÿ': u'y', + + u'À': u'A', u'Á': u'A', u'Â': u'A', u'Ã': u'A', u'Ä': u'A', u'Å': u'A', + u'Æ': u'AE', u'Ç': u'C', u'È': u'E', u'É': u'E', u'Ê': u'E', u'Ë': u'E', + u'Ì': u'I', u'Í': u'I', u'Î': u'I', u'Ï': u'I', u'Ð': u'D', u'Ñ': u'N', + u'Ò': u'O', u'Ó': u'O', u'Ô': u'O', u'Õ': u'O', u'Ö': u'O', u'Ő': u'O', + u'Ø': u'O', u'Ù': u'U', u'Ú': u'U', u'Û': u'U', u'Ü': u'U', u'Ű': u'U', + u'Ý': u'Y', u'Þ': u'TH', u'ß': u'ss' +} + +greek = { + u'α': u'a', u'β': u'b', u'γ': u'g', u'δ': u'd', u'ε': u'e', u'ζ': u'z', + u'η': u'h', u'θ': u'8', u'ι': u'i', u'κ': u'k', u'λ': u'l', u'μ': u'm', + u'ν': u'n', u'ξ': u'3', u'ο': u'o', u'π': u'p', u'ρ': u'r', u'σ': u's', + u'τ': u't', u'υ': u'y', u'φ': u'f', u'χ': u'x', u'ψ': u'ps', u'ω': u'w', + u'ά': u'a', u'έ': u'e', u'ί': u'i', u'ό': u'o', u'ύ': u'y', u'ή': u'h', + u'ώ': u'w', u'ς': u's', u'ϊ': u'i', u'ΰ': u'y', u'ϋ': u'y', u'ΐ': u'i', + + u'Α': u'A', u'Β': u'B', u'Γ': u'G', u'Δ': u'D', u'Ε': u'E', u'Ζ': u'Z', + u'Η': u'H', u'Θ': u'8', u'Ι': u'I', u'Κ': u'K', u'Λ': u'L', u'Μ': u'M', + u'Ν': u'N', u'Ξ': u'3', u'Ο': u'O', u'Π': u'P', u'Ρ': u'R', u'Σ': u'S', + u'Τ': u'T', u'Υ': u'Y', u'Φ': u'F', u'Χ': u'X', u'Ψ': u'PS', u'Ω': u'W', + u'Ά': u'A', u'Έ': u'E', u'Ί': u'I', u'Ό': u'O', u'Ύ': u'Y', u'Ή': u'H', + u'Ώ': u'W', u'Ϊ': u'I', u'Ϋ': u'Y' +} + +turkish = { + u'ş': u's', u'Ş': u'S', u'ı': u'i', u'İ': u'I', u'ç': u'c', u'Ç': u'C', + u'ü': u'u', u'Ü': u'U', u'ö': u'o', u'Ö': u'O', u'ğ': u'g', u'Ğ': u'G' +} + +russian = ( + {u'юй': u'yuy', u'ей': u'yay', + u'Юй': u'Yuy', u'Ей': u'Yay'}, + { + u'а': u'a', u'б': u'b', u'в': u'v', u'г': u'g', u'д': u'd', u'е': u'e', + u'ё': u'yo', u'ж': u'zh', u'з': u'z', u'и': u'i', u'й': u'y', u'к': u'k', + u'л': u'l', u'м': u'm', u'н': u'n', u'о': u'o', u'п': u'p', u'р': u'r', + u'с': u's', u'т': u't', u'у': u'u', u'ф': u'f', u'х': u'h', u'ц': u'c', + u'ч': u'ch', u'ш': u'sh', u'щ': u'sh', u'ъ': u'', u'ы': u'y', u'ь': u'', + u'э': u'e', u'ю': u'yu', u'я': u'ya', + + u'А': u'A', u'Б': u'B', u'В': u'V', u'Г': u'G', u'Д': u'D', u'Е': u'E', + u'Ё': u'Yo', u'Ж': u'Zh', u'З': u'Z', u'И': u'I', u'Й': u'Y', u'К': u'K', + u'Л': u'L', u'М': u'M', u'Н': u'N', u'О': u'O', u'П': u'P', u'Р': u'R', + u'С': u'S', u'Т': u'T', u'У': u'U', u'Ф': u'F', u'Х': u'H', u'Ц': u'C', + u'Ч': u'Ch', u'Ш': u'Sh', u'Щ': u'Sh', u'Ъ': u'', u'Ы': u'Y', u'Ь': u'', + u'Э': u'E', u'Ю': u'Yu', u'Я': u'Ya' +}) + +ukrainian = (russian[0].copy(), { + u'Є': u'Ye', u'І': u'I', u'Ї': u'Yi', u'Ґ': u'G', + u'є': u'ye', u'і': u'i', u'ї': u'yi', u'ґ': u'g' +}) +ukrainian[1].update(russian[1]) + +czech = { + u'č': u'c', u'ď': u'd', u'ě': u'e', u'ň': u'n', u'ř': u'r', u'š': u's', + u'ť': u't', u'ů': u'u', u'ž': u'z', + u'Č': u'C', u'Ď': u'D', u'Ě': u'E', u'Ň': u'N', u'Ř': u'R', u'Š': u'S', + u'Ť': u'T', u'Ů': u'U', u'Ž': u'Z' +} + +polish = { + u'ą': u'a', u'ć': u'c', u'ę': u'e', u'ł': u'l', u'ń': u'n', u'ó': u'o', + u'ś': u's', u'ź': u'z', u'ż': u'z', + u'Ą': u'A', u'Ć': u'C', u'Ę': u'e', u'Ł': u'L', u'Ń': u'N', u'Ó': u'o', + u'Ś': u'S', u'Ź': u'Z', u'Ż': u'Z' +} + +latvian = { + u'ā': u'a', u'č': u'c', u'ē': u'e', u'ģ': u'g', u'ī': u'i', u'ķ': u'k', + u'ļ': u'l', u'ņ': u'n', u'š': u's', u'ū': u'u', u'ž': u'z', + u'Ā': u'A', u'Č': u'C', u'Ē': u'E', u'Ģ': u'G', u'Ī': u'i', u'Ķ': u'k', + u'Ļ': u'L', u'Ņ': u'N', u'Š': u'S', u'Ū': u'u', u'Ž': u'Z' +} + + + +ascii_str = u'''_0123456789 +abcdefghijklmnopqrstuvwxyz +ABCDEFGHIJKLMNOPQRSTUVWXYZ +!"#$%&'()*+,_-./:;<=>?@[\\]^`{|}~ \t\n\r\x0b\x0c''' + +ascii = ({}, dict(zip(ascii_str, ascii_str))) +for t in [latin, greek, turkish, russian, ukrainian, czech, polish, latvian]: + if isinstance(t, dict): + t = ({}, t) + ascii[0].update(t[0]) + ascii[1].update(t[1]) +ascii[1][None] = u'_' +del t + + +id = (ascii[0].copy(), ascii[1].copy()) +for c in '''!"#$%&'()*+,_-./:;<=>?@[\\]^`{|}~ \t\n\r\x0b\x0c''': + del id[1][c] + + +def trans(input, table=ascii): + '''Translate unicode string, using 'table'. + Table may be tuple (diphthongs, other) or dict (other).''' + if not isinstance(input, unicode): + raise TypeError, 'trans codec support only unicode string, %r given.' \ + % type(input) + if isinstance(table, dict): + table = ({}, table) + + first = input + for diphthong, value in table[0].items(): + first = first.replace(diphthong, value) + + second = u'' + for char in first: + second += table[1].get(char, table[1].get(None, u'_')) + + return second, len(second) + +tables = {'ascii': ascii, 'text': ascii, 'id': id} + +import codecs + +def encode(input, errors='strict', table_name='ascii'): + try: + table = tables[table_name] + except KeyError: + raise ValueError, 'Table "%s" not found in tables!' % table_name + else: + return trans(input, table) + +def no_decode(input, errors='strict'): + raise TypeError("trans codec does not support decode.") + +def trans_codec(enc): + if enc == 'trans': + return codecs.CodecInfo(encode, no_decode) +# else: + try: + enc_name, table_name = enc.split('/', 1) + except ValueError: + return None + if enc_name != 'trans': + return None + if table_name not in tables: + raise ValueError, 'Table "%s" not found in tables!' % table_name + + return codecs.CodecInfo(lambda i, e='strict': encode(i, e, table_name), + no_decode) + +codecs.register(trans_codec) -- 1.7.9.5