Source code for dictdumper.plist

# -*- coding: utf-8 -*-
"""dumper a PLIST file

:mod:`dictdumper.plist` contains :class:`~dictdumper.plist.PLIST`
only, which dumpers an Apple property list (PLIST) file. Usage
sample is described as below.

.. code:: python

    >>> dumper = PLIST(file_name)
    >>> dumper(content_dict_1, name=content_name_1)
    >>> dumper(content_dict_2, name=content_name_2)
    ............

"""
# Dumper for PLIST files
# Write a macOS Property List file

import base64
import datetime
import os

from dictdumper._types import bytes_type, str_type
from dictdumper.xml import XML

__all__ = ['PLIST']

#: PLIST head string.
_HEADER_START = '''\
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
'''

#: PLIST tail string.
_HEADER_END = '''\
</dict>
</plist>
'''


[docs]class PLIST(XML): """Dump Apple property list (PLIST) format file. .. code:: python >>> dumper = PLIST(file_name) >>> dumper(content_dict_1, name=content_name_1) >>> dumper(content_dict_2, name=content_name_2) ............ Attributes: _file (str): output file name _sptr (int): indicates start of appending point (file pointer) _tctr (int): tab level counter _hsrt (str): start string (:data:`~dictdumper.plist._HEADER_START`) _hend (str): end string (:data:`~dictdumper.plist._HEADER_END`) .. note:: Terminology: .. code:: value ::= array | dict | string | data | date | integer | real | bool array ::= "<array>" value* "</array>" dict ::= "<dict>" ("<key>" str "</key>" value)* "</dict>" string ::= "<string>" str "</string>" data ::= "<data>" bytes "</data>" date ::= "<date>" datetime "</date>" integer ::= "<integer>" int "</integer>" real ::= "<real>" float "</real>" bool ::= "<true/>" | "<false/>" """ ########################################################################## # Properties. ########################################################################## @property def kind(self): """File format of current dumper. :rtype: Literal['plist'] """ return 'plist' ########################################################################## # Type codes. ########################################################################## #: Tuple[Tuple[type, str]]: Type codes. __type__ = ( # string (str_type, 'string'), # bool (bool, 'bool'), # dict (dict, 'dict'), # date (datetime.date, 'date'), (datetime.datetime, 'date'), # integer (int, 'integer'), # real (float, 'real'), # data (bytes_type, 'data'), # array (list, 'array'), ) ########################################################################## # Attributes. ########################################################################## #: PLIST head string. _hsrt = _HEADER_START #: PLIST tail string. _hend = _HEADER_END ########################################################################## # Utilities. ##########################################################################
[docs] def _encode_value(self, o): # pylint: disable=unused-argument """Check content type for function call. Args: o (Any): object to convert Returns: Any: the converted object See Also: The function is a direct wrapper for :meth:`~dictdumper.dumper.Dumper.object_hook`. Notes: The function will by default converts :obj:`bytearray`, ``None``, :obj:`memoryview`, :obj:`tuple`, :obj:`set`, :obj:`frozenset` to PLIST serialisable data. """ if o is None: return self.make_object(o, 'None') if isinstance(o, bytearray): return self.make_object(o, bytes_type(o)) if isinstance(o, memoryview): return self.make_object(o, o.tobytes()) if isinstance(o, (tuple, set, frozenset)): return self.make_object(o, list(o)) return self.object_hook(o)
[docs] def _append_value(self, value, file, name): """Call this function to write contents. Args: value (Dict[str, Any]): content to be dumped file (io.TextIOWrapper): output file name (str): name of current content block """ tabs = '\t' * self._tctr keys = '{tabs}<key>{name}</key>\n'.format(tabs=tabs, name=name) file.seek(self._sptr, os.SEEK_SET) file.write(keys) self._append_dict(value, file)
########################################################################## # Functions. ##########################################################################
[docs] def _append_dict(self, value, file): """Call this function to write dict contents. Args: value (Dict[str, Any]): content to be dumped file (io.TextIOWrapper): output file """ tabs = '\t' * self._tctr labs = '{tabs}<dict>\n'.format(tabs=tabs) file.write(labs) self._tctr += 1 for (item, text) in value.items(): if text is None: continue tabs = '\t' * self._tctr keys = '{tabs}<key>{item}</key>\n'.format(tabs=tabs, item=item) file.write(keys) enc_text = self._encode_value(text) func = self._encode_func(enc_text) func(enc_text, file) self._tctr -= 1 tabs = '\t' * self._tctr labs = '{tabs}</dict>\n'.format(tabs=tabs) file.write(labs)
[docs] def _append_array(self, value, file): """Call this function to write array contents. Args: value (List[Any]): content to be dumped file (io.TextIOWrapper): output file """ tabs = '\t' * self._tctr labs = '{tabs}<array>\n'.format(tabs=tabs) file.write(labs) self._tctr += 1 for item in value: if item is None: continue enc_text = self._encode_value(item) func = self._encode_func(enc_text) func(enc_text, file) self._tctr -= 1 tabs = '\t' * self._tctr labs = '{tabs}</array>\n'.format(tabs=tabs) file.write(labs)
[docs] def _append_string(self, value, file): """Call this function to write string contents. Args: value (str): content to be dumped file (io.TextIOWrapper): output file """ tabs = '\t' * self._tctr text = value labs = '{tabs}<string>{text}</string>\n'.format(tabs=tabs, text=text) file.write(labs)
[docs] def _append_data(self, value, file): """Call this function to write data contents. Args: value (bytes): content to be dumped file (io.TextIOWrapper): output file """ # binascii.b2a_base64(value) -> plistlib.Data # binascii.a2b_base64(Data) -> value(bytes) tabs = '\t' * self._tctr text = base64.b64encode(value).decode() labs = '{tabs}<data>{text}</data>\n'.format(tabs=tabs, text=text) file.write(labs)
[docs] def _append_date(self, value, file): """Call this function to write date contents. Args: value (Union[datetime.date, datetime.datetime]): content to be dumped file (io.TextIOWrapper): output file """ tabs = '\t' * self._tctr text = value.strftime(r'%Y-%m-%dT%H:%M:%S.%fZ') labs = '{tabs}<date>{text}</date>\n'.format(tabs=tabs, text=text) file.write(labs)
[docs] def _append_integer(self, value, file): """Call this function to write integer contents. Args: value (int): content to be dumped file (io.TextIOWrapper): output file """ tabs = '\t' * self._tctr text = value labs = '{tabs}<integer>{text}</integer>\n'.format(tabs=tabs, text=text) file.write(labs)
[docs] def _append_real(self, value, file): """Call this function to write real contents. Args: value (float): content to be dumped file (io.TextIOWrapper): output file """ tabs = '\t' * self._tctr text = value labs = '{tabs}<real>{text}</real>\n'.format(tabs=tabs, text=text) file.write(labs)
[docs] def _append_bool(self, value, file): """Call this function to write bool contents. Args: value (bool): content to be dumped file (io.TextIOWrapper): output file """ tabs = '\t' * self._tctr text = '<true/>' if value else '<false/>' labs = '{tabs}{text}\n'.format(tabs=tabs, text=text) file.write(labs)