cctool

A tool for managing contacts and calendars.
git clone https://git.ce9e.org/cctool.git

commit
3e4fb5fe618fa504c517a29b139c038e46034f5a
parent
e2d5a20dde83949cc3801aad579caad9151cbe40
Author
Tobias Bengfort <tobias.bengfort@gmx.net>
Date
2015-05-08 17:04
normalize field keys

Diffstat

M cctool.py 55 +++++++++++++++++++++++++++++++++++++++----------------
M tests.py 4 ++--

2 files changed, 41 insertions, 18 deletions


diff --git a/cctool.py b/cctool.py

@@ -173,6 +173,21 @@ def merged(data, key):
  173   173 	return list(tmp.values()) + missing
  174   174 
  175   175 
   -1   176 def map_keys(mdict, _map, reverse=False, exclusive=True):
   -1   177 	if reverse:
   -1   178 		_map = dict((value, key) for key, value in _map.items())
   -1   179 
   -1   180 	outdict = MultiDict()
   -1   181 
   -1   182 	for key in mdict:
   -1   183 		if key in _map:
   -1   184 			outdict.append(_map[key], mdict[key])
   -1   185 		elif not exclusive:
   -1   186 			outdict.append(key, mdict[key])
   -1   187 
   -1   188 	return outdict
   -1   189 
   -1   190 
  176   191 class Format(object):
  177   192 	"""Baseclass with an API similar to the marshal, pickle and json modules.
  178   193 
@@ -214,13 +229,16 @@ class BSDCal(Format):
  214   229 
  215   230 
  216   231 class ICal(Format):
  217    -1 	fields = ['attach', 'categories', 'class', 'comment', 'description', 'geo',
  218    -1 		'location', 'percent-complete', 'priority', 'resources', 'status',
  219    -1 		'summary', 'completed', 'dtend', 'due', 'dtstart', 'duration', 'freebusy',
  220    -1 		'transp', 'tzid', 'tzname', 'tzoffsetfrom', 'tzoffsetto', 'tzurl',
  221    -1 		'attendee', 'contact', 'organizer', 'recurrence-id', 'related-to', 'url',
  222    -1 		'uid', 'exdate', 'rdate', 'rrule', 'action', 'repeat', 'trigger',
  223    -1 		'created', 'dtstamp', 'last-modified', 'sequence']
   -1   232 	fields = {
   -1   233 		'categories': 'tag',
   -1   234 		'comment': 'comment',
   -1   235 		'description': 'description',
   -1   236 		'location': 'location',
   -1   237 		'summary': 'summary',
   -1   238 		'dtend': 'dtend',
   -1   239 		'dtstart': 'dtstart',
   -1   240 		'url': 'url',
   -1   241 	}
  224   242 
  225   243 	@classmethod
  226   244 	def _iter_events(cls, component):
@@ -264,7 +282,7 @@ class ICal(Format):
  264   282 					except ValueError:
  265   283 						break
  266   284 			else:
  267    -1 				yield d
   -1   285 				yield map_keys(d, cls.fields)
  268   286 
  269   287 	@classmethod
  270   288 	def dump(cls, data, fh):
@@ -275,8 +293,9 @@ class ICal(Format):
  275   293 		calendar.add('prodid', '-//XI//NONSGML CCTOOL//')
  276   294 		calendar.add('version', '2.0')
  277   295 
  278    -1 		for event in data:
   -1   296 		for _event in data:
  279   297 			vevent = icalendar.Event()
   -1   298 			event = map_keys(_event, cls.fields, reverse=True)
  280   299 			for key in event:
  281   300 				if key in cls.fields:
  282   301 					for value in event[key]:
@@ -287,10 +306,11 @@ class ICal(Format):
  287   306 
  288   307 
  289   308 class ABook(Format):
  290    -1 	fields = ['name', 'nick', 'bday', 'email', 'url', 'tag',
   -1   309 	fields = dict((x, x) for x in [
   -1   310 		'name', 'nick', 'bday', 'email', 'url', 'tag',
  291   311 		'address_lines', 'city', 'state', 'zip', 'country',
  292   312 		'phone', 'workphone', 'mobile',
  293    -1 		'xmpp', 'icq', 'msn', 'twitter', 'pgp']
   -1   313 		'xmpp', 'icq', 'msn', 'twitter', 'pgp'])
  294   314 
  295   315 	@classmethod
  296   316 	def load(cls, fh):
@@ -307,14 +327,15 @@ class ABook(Format):
  307   327 						d[key] = [datetime.strptime(value, '%Y-%m-%d')]
  308   328 					else:
  309   329 						d[key] = value.split(u',')
  310    -1 				yield d
   -1   330 				yield map_keys(d, cls.fields)
  311   331 
  312   332 	@classmethod
  313   333 	def dump(cls, data, fh):
  314   334 		_fh = codecs.getwriter('utf8')(fh)
  315   335 		cp = ConfigParser()
  316   336 		i = 0
  317    -1 		for item in data:
   -1   337 		for _item in data:
   -1   338 			item = map_keys(_item, cls.fields, reverse=True)
  318   339 			section = _str(i)
  319   340 			cp.add_section(section)
  320   341 			for key in item:
@@ -346,8 +367,10 @@ if not isinstance(ldif, Exception):
  346   367 
  347   368 
  348   369 class LDIF(Format):
  349    -1 	fields = ['dn', 'objectclass', 'modifytimestamp',
  350    -1 		'mail', 'givenName', 'sn', 'cn']
   -1   370 	fields = {
   -1   371 		'cn': 'name',
   -1   372 		'mail': 'email',
   -1   373 	}
  351   374 
  352   375 	@classmethod
  353   376 	def load(cls, fh):
@@ -360,7 +383,7 @@ class LDIF(Format):
  360   383 			log.warning("ValueError after reading %i records: %s",
  361   384 				parser.records_read, err)
  362   385 		for entry in parser.entries.values():
  363    -1 			yield MultiDict(entry)
   -1   386 			yield map_keys(MultiDict(entry), cls.fields)
  364   387 
  365   388 
  366   389 class DateTimeJSONEncoder(json.JSONEncoder):

diff --git a/tests.py b/tests.py

@@ -94,8 +94,8 @@ class TestBSDCal(_TestFormat):
   94    94 class TestICal(_TestFormat):
   95    95 	def setUp(self):
   96    96 		self.format = cctool.ICal()
   97    -1 		self.data = [cctool.MultiDict({u'uid': [u'20140519T210153Z-13022@tobias-eee']})]
   98    -1 		self.text = b'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//XI//NONSGML CCTOOL//\r\nBEGIN:VEVENT\r\nUID:20140519T210153Z-13022@tobias-eee\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n'
   -1    97 		self.data = [cctool.MultiDict({u'summary': [u'lorem ipsum']})]
   -1    98 		self.text = b'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//XI//NONSGML CCTOOL//\r\nBEGIN:VEVENT\r\nSUMMARY:lorem ipsum\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n'
   99    99 
  100   100 
  101   101 class TestABook(_TestFormat):