cctool

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

commit
e820676a8df7d07f7bcc31385ec39a074b575e72
parent
e300f902ea7cb7d0a0ffd3868e423a64be03fa46
Author
Tobias Bengfort <tobias.bengfort@gmx.net>
Date
2015-05-08 13:14
strict encoding

Diffstat

M cctool.py 40 ++++++++++++++++++++++------------------
M tests.py 24 ++++++++++++++++++------

2 files changed, 40 insertions, 24 deletions


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

@@ -35,11 +35,8 @@ from collections import OrderedDict
   35    35 import json
   36    36 from datetime import datetime
   37    37 import pickle
   38    -1 
   39    -1 try:
   40    -1 	from StringIO import StringIO
   41    -1 except ImportError:
   42    -1 	from io import StringIO
   -1    38 import codecs
   -1    39 from io import BytesIO
   43    40 
   44    41 try:
   45    42 	from ConfigParser import RawConfigParser as ConfigParser
@@ -155,7 +152,11 @@ def merged(data, key):
  155   152 
  156   153 
  157   154 class Format(object):
  158    -1 	"""Baseclass with an API similar to the marshal, pickle and json modules."""
   -1   155 	"""Baseclass with an API similar to the marshal, pickle and json modules.
   -1   156 
   -1   157 	:py:meth:`load` takes a bytes stream and returns a :py:class:`MultiDict`.
   -1   158 	:py:meth:`dump` does the reverse.
   -1   159 	"""
  159   160 
  160   161 	@classmethod
  161   162 	def load(cls, fh):
@@ -163,7 +164,7 @@ class Format(object):
  163   164 
  164   165 	@classmethod
  165   166 	def loads(cls, s):
  166    -1 		return cls.load(StringIO(s))
   -1   167 		return cls.load(BytesIO(s))
  167   168 
  168   169 	@classmethod
  169   170 	def dump(cls, data, fh):
@@ -171,7 +172,7 @@ class Format(object):
  171   172 
  172   173 	@classmethod
  173   174 	def dumps(cls, data):
  174    -1 		fh = StringIO()
   -1   175 		fh = BytesIO()
  175   176 		cls.dump(data, fh)
  176   177 		return fh.getvalue()
  177   178 
@@ -179,14 +180,15 @@ class Format(object):
  179   180 class BSDCal(Format):
  180   181 	@classmethod
  181   182 	def dump(cls, data, fh):
   -1   183 		_fh = codecs.getwriter('utf8')(fh)
  182   184 		for item in data:
  183   185 			if u'dtstart' in item and u'summary' in item:
  184   186 				dt = item.first('dtstart')
  185   187 				if dt.year == datetime.today().year:
  186    -1 					fh.write('%s\t%s\n' % (dt.strftime('%m/%d'), item.join('summary')))
   -1   188 					_fh.write('%s\t%s\n' % (dt.strftime('%m/%d'), item.join('summary')))
  187   189 			if u'bday' in item and u'name' in item:
  188   190 				dt = item.first('bday')
  189    -1 				fh.write('%s\t%s\n' % (dt.strftime('%m/%d*'), item.join('name')))
   -1   191 				_fh.write('%s\t%s\n' % (dt.strftime('%m/%d*'), item.join('name')))
  190   192 
  191   193 
  192   194 class ICal(Format):
@@ -270,8 +272,9 @@ class ABook(Format):
  270   272 
  271   273 	@classmethod
  272   274 	def load(cls, fh):
   -1   275 		_fh = codecs.getreader('utf8')(fh)
  273   276 		config_parser = ConfigParser()
  274    -1 		config_parser.readfp(fh)
   -1   277 		config_parser.readfp(_fh)
  275   278 		for section in config_parser.sections():
  276   279 			if section != u'format':
  277   280 				d = MultiDict()
@@ -286,6 +289,7 @@ class ABook(Format):
  286   289 
  287   290 	@classmethod
  288   291 	def dump(cls, data, fh):
   -1   292 		_fh = codecs.getwriter('utf8')(fh)
  289   293 		cp = ConfigParser()
  290   294 		i = 0
  291   295 		for item in data:
@@ -306,7 +310,7 @@ class ABook(Format):
  306   310 				elif key in ['cn']:
  307   311 					cp.set(section, 'name', item.join(key))
  308   312 			i += 1
  309    -1 		cp.write(fh)
   -1   313 		cp.write(_fh)
  310   314 
  311   315 
  312   316 if not isinstance(ldif, Exception):
@@ -354,11 +358,13 @@ class DateTimeJSONEncoder(json.JSONEncoder):
  354   358 class JSON(Format):
  355   359 	@classmethod
  356   360 	def load(cls, fh):
  357    -1 		return [MultiDict(i) for i in json.load(fh)]
   -1   361 		_fh = codecs.getreader('utf8')(fh)
   -1   362 		return [MultiDict(i) for i in json.load(_fh)]
  358   363 
  359   364 	@classmethod
  360   365 	def dump(cls, data, fh):
  361    -1 		json.dump(list(data), fh, indent=4, cls=DateTimeJSONEncoder)
   -1   366 		_fh = codecs.getwriter('utf8')(fh)
   -1   367 		json.dump(list(data), _fh, indent=4, cls=DateTimeJSONEncoder)
  362   368 
  363   369 
  364   370 class Pickle(Format):
@@ -417,8 +423,6 @@ def main():
  417   423 	informats, outformats = formats()
  418   424 	args = parse_args()
  419   425 
  420    -1 	sys.setdefaultencoding('utf-8')
  421    -1 
  422   426 	outformat = get_outformat(args)
  423   427 
  424   428 	data = []
@@ -428,7 +432,7 @@ def main():
  428   432 		else:
  429   433 			informat = get_informat(filename)
  430   434 
  431    -1 		infile = sys.stdin if filename == '-' else open(filename)
   -1   435 		infile = sys.stdin if filename == '-' else open(filename, 'rb')
  432   436 		try:
  433   437 			data += informats[informat]().load(infile)
  434   438 		except Exception as err:
@@ -443,7 +447,7 @@ def main():
  443   447 	if args.sort is not None:
  444   448 		data = sorted(data, key=lambda x: x[args.sort])
  445   449 
  446    -1 	outfile = sys.stdout if args.output is None else open(args.output, 'w')
   -1   450 	outfile = sys.stdout if args.output is None else open(args.output, 'wb')
  447   451 	try:
  448   452 		outformats[outformat]().dump(data, outfile)
  449   453 	except Exception as err:

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

@@ -84,7 +84,7 @@ class TestBSDCal(_TestFormat):
   84    84 			cctool.MultiDict({'dtstart': [dt], 'summary': ['foo']}),
   85    85 			cctool.MultiDict({'bday': [dt], 'name': ['bar']}),
   86    86 		]
   87    -1 		self.text = '01/01\tfoo\n01/01*\tbar\n'
   -1    87 		self.text = b'01/01\tfoo\n01/01*\tbar\n'
   88    88 
   89    89 	def test_load(self):
   90    90 		pass
@@ -95,7 +95,7 @@ class TestICal(_TestFormat):
   95    95 	def setUp(self):
   96    96 		self.format = cctool.ICal()
   97    97 		self.data = [cctool.MultiDict({u'uid': [u'20140519T210153Z-13022@tobias-eee']})]
   98    -1 		self.text = '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    98 		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'
   99    99 
  100   100 
  101   101 class TestABook(_TestFormat):
@@ -105,7 +105,7 @@ class TestABook(_TestFormat):
  105   105 			('name', ['foo']),
  106   106 			('bday', [datetime(1970, 1, 1)]),
  107   107 		])]
  108    -1 		self.text = '[0]\nname = foo\nbday = 1970-01-01\n\n'
   -1   108 		self.text = b'[0]\nname = foo\nbday = 1970-01-01\n\n'
  109   109 
  110   110 
  111   111 @unittest.skipIf(isinstance(cctool.ldif, Exception), 'ldif not available')
@@ -113,7 +113,7 @@ class TestLDIF(_TestFormat):
  113   113 	def setUp(self):
  114   114 		self.format = cctool.LDIF()
  115   115 		self.data = [cctool.MultiDict({'dn': ['foo']})]
  116    -1 		self.text = '[0]\ndn = foo\n\n'
   -1   116 		self.text = b'[0]\ndn = foo\n\n'
  117   117 
  118   118 	def test_dump(self):
  119   119 		pass
@@ -122,13 +122,25 @@ class TestLDIF(_TestFormat):
  122   122 class TestJSON(_TestFormat):
  123   123 	def setUp(self):
  124   124 		self.format = cctool.JSON()
  125    -1 		self.text = '[\n    {\n        "name": [\n            "foo"\n        ]\n    }\n]'
   -1   125 		self.text = b'[\n    {\n        "name": [\n            "foo"\n        ]\n    }\n]'
  126   126 
  127   127 
  128   128 class TestPickle(_TestFormat):
  129   129 	def setUp(self):
  130   130 		self.format = cctool.Pickle()
  131    -1 		self.text = '(lp0\nccctool\nMultiDict\np1\n((lp2\n(lp3\nS\'name\'\np4\na(lp5\nS\'foo\'\np6\naaatp7\nRp8\na.'
   -1   131 
   -1   132 	# the serialization is different in py3, even with same protocol.
   -1   133 	# so we only test that the data is not changes by encode/decode.
   -1   134 	def test_combined(self):
   -1   135 		tmp = self.format.dumps(self.data)
   -1   136 		actual = self.format.loads(tmp)
   -1   137 		self.assertEqual(list(actual), self.data)
   -1   138 
   -1   139 	def test_dump(self):
   -1   140 		pass
   -1   141 
   -1   142 	def test_load(self):
   -1   143 		pass
  132   144 
  133   145 
  134   146 class TestArgs(unittest.TestCase):