fontTools.misc.* documentation, part 2 (#1981)

* Document misc.filenames
* Document misc.fixedTools
* Document misc.intTools
* Document misc.loggingTools
* Document misc.macCreatorType
* Document misc.macRes
* Document misc.plistlib
This commit is contained in:
Simon Cozens 2020-06-08 15:53:48 +01:00 committed by GitHub
parent 80dac9c0f4
commit 775dc6074e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 526 additions and 193 deletions

View File

@ -1,8 +1,7 @@
######### ##########################################################
filenames filenames: Implements UFO User Name to File Name Algorithm
######### ##########################################################
.. automodule:: fontTools.misc.filenames .. automodule:: fontTools.misc.filenames
:inherited-members: :members: userNameToFileName
:members:
:undoc-members: :undoc-members:

View File

@ -1,6 +1,6 @@
########## ######################################################
fixedTools fixedTools: Tools for working with fixed-point numbers
########## ######################################################
.. automodule:: fontTools.misc.fixedTools .. automodule:: fontTools.misc.fixedTools
:inherited-members: :inherited-members:

View File

@ -21,6 +21,7 @@ utilities by fontTools, but some of which may be more generally useful.
loggingTools loggingTools
macCreatorType macCreatorType
macRes macRes
plistlib
psCharStrings psCharStrings
psLib psLib
psOperators psOperators

View File

@ -1,8 +1,6 @@
######## ###############################################
intTools intTools: Tools for working with integer values
######## ###############################################
.. automodule:: fontTools.misc.intTools .. automodule:: fontTools.misc.intTools
:inherited-members:
:members: :members:
:undoc-members:

View File

@ -1,8 +1,7 @@
############ ###################################################################
loggingTools loggingTools: tools for interfacing with the Python logging package
############ ###################################################################
.. automodule:: fontTools.misc.loggingTools .. automodule:: fontTools.misc.loggingTools
:inherited-members:
:members: :members:
:undoc-members: :undoc-members:

View File

@ -1,8 +1,9 @@
############## ##############################################################
macCreatorType macCreatorType: Functions for working with Mac file attributes
############## ##############################################################
This module requires the `xattr <https://pypi.org/project/xattr/>`_ module
to be installed in order to function correctly.
.. automodule:: fontTools.misc.macCreatorType .. automodule:: fontTools.misc.macCreatorType
:inherited-members:
:members: :members:
:undoc-members:

View File

@ -1,8 +1,11 @@
###### ############################################
macRes macRes: Tools for reading Mac resource forks
###### ############################################
Classic Mac OS files are made up of two parts - the "data fork" which contains the file contents proper, and the "resource fork" which contains a number of structured data items called "resources". Some fonts, such as Mac "font suitcases" and Type 1 LWFN fonts, still use the resource fork for this kind of structured data, and so to read them, fontTools needs to have access to resource forks.
The Inside Macintosh volume `More Macintosh Toolbox <https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf#page=34>`_ explains the structure of resource and data forks.
.. automodule:: fontTools.misc.macRes .. automodule:: fontTools.misc.macRes
:inherited-members: :members: ResourceReader, Resource
:members: :member-order: bysource
:undoc-members:

View File

@ -0,0 +1,9 @@
#########################################
plistlib: Tools for handling .plist files
#########################################
.. automodule:: fontTools.misc.plistlib
:members: totree, fromtree, load, loads, dump, dumps
.. autoclass:: fontTools.misc.plistlib.Data
:members:

View File

@ -1,19 +1,23 @@
""" """
User name to file name conversion based on the UFO 3 spec: This module implements the algorithm for converting between a "user name" -
http://unifiedfontobject.org/versions/ufo3/conventions/ something that a user can choose arbitrarily inside a font editor - and a file
name suitable for use in a wide range of operating systems and filesystems.
The code was copied from: The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py provides an example of an algorithm for such conversion, which avoids illegal
characters, reserved file names, ambiguity between upper- and lower-case
characters, and clashes with existing files.
Author: Tal Leming This code was originally copied from
Copyright (c) 2005-2016, The RoboFab Developers: `ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
Erik van Blokland by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
Tal Leming
Just van Rossum - Erik van Blokland
- Tal Leming
- Just van Rossum
""" """
from fontTools.misc.py23 import basestring, unicode from fontTools.misc.py23 import basestring, unicode
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ") illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
illegalCharacters += [chr(i) for i in range(1, 32)] illegalCharacters += [chr(i) for i in range(1, 32)]
illegalCharacters += [chr(0x7F)] illegalCharacters += [chr(0x7F)]
@ -27,9 +31,24 @@ class NameTranslationError(Exception):
def userNameToFileName(userName, existing=[], prefix="", suffix=""): def userNameToFileName(userName, existing=[], prefix="", suffix=""):
""" """Converts from a user name to a file name.
existing should be a case-insensitive list
of all existing file names. Takes care to avoid illegal characters, reserved file names, ambiguity between
upper- and lower-case characters, and clashes with existing files.
Args:
userName (str): The input file name.
existing: A case-insensitive list of all existing file names.
prefix: Prefix to be prepended to the file name.
suffix: Suffix to be appended to the file name.
Returns:
A suitable filename.
Raises:
NameTranslationError: If no suitable name could be generated.
Examples::
>>> userNameToFileName("a") == "a" >>> userNameToFileName("a") == "a"
True True

View File

@ -1,4 +1,20 @@
"""fontTools.misc.fixedTools.py -- tools for working with fixed numbers. """
The `OpenType specification <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types>`_
defines two fixed-point data types:
``Fixed``
A 32-bit signed fixed-point number with a 16 bit twos-complement
magnitude component and 16 fractional bits.
``F2DOT14``
A 16-bit signed fixed-point number with a 2 bit twos-complement
magnitude component and 14 fractional bits.
To support reading and writing data with these data types, this module provides
functions for converting between fixed-point, float and string representations.
.. data:: MAX_F2DOT14
The maximum value that can still fit in an F2Dot14. (1.99993896484375)
""" """
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
@ -8,6 +24,7 @@ import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
__all__ = [ __all__ = [
"MAX_F2DOT14",
"otRound", "otRound",
"fixedToFloat", "fixedToFloat",
"floatToFixed", "floatToFixed",
@ -21,25 +38,45 @@ __all__ = [
] ]
# the max value that can still fit in an F2Dot14:
# 1.99993896484375
MAX_F2DOT14 = 0x7FFF / (1 << 14) MAX_F2DOT14 = 0x7FFF / (1 << 14)
def otRound(value): def otRound(value):
"""Round float value to nearest integer towards +Infinity. """Round float value to nearest integer towards ``+Infinity``.
For fractional values of 0.5 and higher, take the next higher integer;
The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_)
defines the required method for converting floating point values to
fixed-point. In particular it specifies the following rounding strategy:
for fractional values of 0.5 and higher, take the next higher integer;
for other fractional values, truncate. for other fractional values, truncate.
https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview This function rounds the floating-point value according to this strategy
https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166 in preparation for conversion to fixed-point.
Args:
value (float): The input floating-point value.
Returns
float: The rounded value.
""" """
# See this thread for how we ended up with this implementation:
# https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
return int(math.floor(value + 0.5)) return int(math.floor(value + 0.5))
def fixedToFloat(value, precisionBits): def fixedToFloat(value, precisionBits):
"""Converts a fixed-point number to a float given the number of """Converts a fixed-point number to a float given the number of
precisionBits. Ie. value / (1 << precisionBits) precision bits.
Args:
value (int): Number in fixed-point format.
precisionBits (int): Number of precision bits.
Returns:
Floating point value.
Examples::
>>> import math >>> import math
>>> f = fixedToFloat(-10139, precisionBits=14) >>> f = fixedToFloat(-10139, precisionBits=14)
@ -51,7 +88,16 @@ def fixedToFloat(value, precisionBits):
def floatToFixed(value, precisionBits): def floatToFixed(value, precisionBits):
"""Converts a float to a fixed-point number given the number of """Converts a float to a fixed-point number given the number of
precisionBits. Ie. round(value * (1 << precisionBits)). precision bits.
Args:
value (float): Floating point value.
precisionBits (int): Number of precision bits.
Returns:
int: Fixed-point representation.
Examples::
>>> floatToFixed(-0.61883544921875, precisionBits=14) >>> floatToFixed(-0.61883544921875, precisionBits=14)
-10139 -10139
@ -62,11 +108,22 @@ def floatToFixed(value, precisionBits):
def floatToFixedToFloat(value, precisionBits): def floatToFixedToFloat(value, precisionBits):
"""Converts a float to a fixed-point number given the number of """Converts a float to a fixed-point number and back again.
precisionBits, round it, then convert it back to float again.
Ie. round(value * (1<<precisionBits)) / (1<<precisionBits)
Note: this **is** equivalent to fixedToFloat(floatToFixed(value)).
By converting the float to fixed, rounding it, and converting it back
to float again, this returns a floating point values which is exactly
representable in fixed-point format.
Note: this **is** equivalent to ``fixedToFloat(floatToFixed(value))``.
Args:
value (float): The input floating point value.
precisionBits (int): Number of precision bits.
Returns:
float: The transformed and rounded value.
Examples::
>>> import math >>> import math
>>> f1 = -0.61884 >>> f1 = -0.61884
>>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14) >>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
@ -80,19 +137,27 @@ def floatToFixedToFloat(value, precisionBits):
def fixedToStr(value, precisionBits): def fixedToStr(value, precisionBits):
"""Converts a fixed-point number with 'precisionBits' number of fractional binary """Converts a fixed-point number to a string representing a decimal float.
digits to a string representing a decimal float, choosing the float that has the
shortest decimal representation (the least number of fractional decimal digits). This chooses the float that has the shortest decimal representation (the least
Eg. to convert a fixed-point number in a 2.14 format, use precisionBits=14: number of fractional decimal digits).
For example, to convert a fixed-point number in a 2.14 format, use
``precisionBits=14``::
>>> fixedToStr(-10139, precisionBits=14) >>> fixedToStr(-10139, precisionBits=14)
'-0.61884' '-0.61884'
This is pretty slow compared to the simple division used in fixedToFloat. This is pretty slow compared to the simple division used in ``fixedToFloat``.
Use sporadically when you need to serialize or print the fixed-point number in Use sporadically when you need to serialize or print the fixed-point number in
a human-readable form. a human-readable form.
NOTE: precisionBits is only supported up to 16. Args:
value (int): The fixed-point value to convert.
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
Returns:
str: A string representation of the value.
""" """
if not value: return "0.0" if not value: return "0.0"
@ -118,10 +183,18 @@ def fixedToStr(value, precisionBits):
def strToFixed(string, precisionBits): def strToFixed(string, precisionBits):
"""Convert the string representation of a decimal float number to a fixed-point """Converts a string representing a decimal float to a fixed-point number.
number with 'precisionBits' fractional binary digits.
E.g. to convert a float string to a 2.14 fixed-point number:
Args:
string (str): A string representing a decimal float.
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
Returns:
int: Fixed-point representation.
Examples::
>>> ## to convert a float string to a 2.14 fixed-point number:
>>> strToFixed('-0.61884', precisionBits=14) >>> strToFixed('-0.61884', precisionBits=14)
-10139 -10139
""" """
@ -130,10 +203,23 @@ def strToFixed(string, precisionBits):
def strToFixedToFloat(string, precisionBits): def strToFixedToFloat(string, precisionBits):
"""Convert a string to a decimal float, by first converting the float to a """Convert a string to a decimal float with fixed-point rounding.
fixed-point number with precisionBits fractional binary digits.
This first converts string to a float, then turns it into a fixed-point
number with ``precisionBits`` fractional binary digits, then back to a
float again.
This is simply a shorthand for fixedToFloat(floatToFixed(float(s))). This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).
Args:
string (str): A string representing a decimal float.
precisionBits (int): Number of precision bits.
Returns:
float: The transformed and rounded value.
Examples::
>>> import math >>> import math
>>> s = '-0.61884' >>> s = '-0.61884'
>>> bits = 14 >>> bits = 14
@ -149,21 +235,43 @@ def strToFixedToFloat(string, precisionBits):
def floatToFixedToStr(value, precisionBits): def floatToFixedToStr(value, precisionBits):
"""Convert float to string using the shortest decimal representation (ie. the least """Convert float to string with fixed-point rounding.
number of fractional decimal digits) to represent the equivalent fixed-point number
with 'precisionBits' fractional binary digits. This uses the shortest decimal representation (ie. the least
number of fractional decimal digits) to represent the equivalent
fixed-point number with ``precisionBits`` fractional binary digits.
It uses fixedToStr under the hood. It uses fixedToStr under the hood.
>>> floatToFixedToStr(-0.61883544921875, precisionBits=14) >>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
'-0.61884' '-0.61884'
Args:
value (float): The float value to convert.
precisionBits (int): Number of precision bits, *up to a maximum of 16*.
Returns:
str: A string representation of the value.
""" """
fixed = otRound(value * (1 << precisionBits)) fixed = otRound(value * (1 << precisionBits))
return fixedToStr(fixed, precisionBits) return fixedToStr(fixed, precisionBits)
def ensureVersionIsLong(value): def ensureVersionIsLong(value):
"""Ensure a table version is an unsigned long (unsigned short major, """Ensure a table version is an unsigned long.
unsigned short minor) instead of a float."""
OpenType table version numbers are expressed as a single unsigned long
comprising of an unsigned short major version and unsigned short minor
version. This function detects if the value to be used as a version number
looks too small (i.e. is less than ``0x10000``), and converts it to
fixed-point using :func:`floatToFixed` if so.
Args:
value (Number): a candidate table version number.
Returns:
int: A table version number, possibly corrected to fixed-point.
"""
if value < 0x10000: if value < 0x10000:
newValue = floatToFixed(value, 16) newValue = floatToFixed(value, 16)
log.warning( log.warning(
@ -174,7 +282,14 @@ def ensureVersionIsLong(value):
def versionToFixed(value): def versionToFixed(value):
"""Converts a table version to a fixed""" """Ensure a table version number is fixed-point.
Args:
value (str): a candidate table version number.
Returns:
int: A table version number, possibly corrected to fixed-point.
"""
value = int(value, 0) if value.startswith("0") else float(value) value = int(value, 0) if value.startswith("0") else float(value)
value = ensureVersionIsLong(value) value = ensureVersionIsLong(value)
return value return value

View File

@ -1,12 +1,25 @@
"""Misc integer tools."""
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
__all__ = ['popCount'] __all__ = ['popCount']
def popCount(v): def popCount(v):
"""Return number of 1 bits in an integer.""" """Return number of 1 bits (population count) of an integer.
If the integer is negative, the number of 1 bits in the
twos-complement representation of the integer is returned. i.e.
``popCount(-30) == 28`` because -30 is::
1111 1111 1111 1111 1111 1111 1110 0010
Uses the algorithm from `HAKMEM item 169 <https://www.inwap.com/pdp10/hbaker/hakmem/hacks.html#item169>`_.
Args:
v (int): Value to count.
Returns:
Number of 1 bits in the binary representation of ``v``.
"""
if v > 0xFFFFFFFF: if v > 0xFFFFFFFF:
return popCount(v >> 32) + popCount(v & 0xFFFFFFFF) return popCount(v >> 32) + popCount(v & 0xFFFFFFFF)

View File

@ -1,7 +1,3 @@
""" fontTools.misc.loggingTools.py -- tools for interfacing with the Python
logging package.
"""
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
import sys import sys
import logging import logging
@ -25,10 +21,18 @@ DEFAULT_FORMATS = {
class LevelFormatter(logging.Formatter): class LevelFormatter(logging.Formatter):
""" Formatter class which optionally takes a dict of logging levels to """Log formatter with level-specific formatting.
Formatter class which optionally takes a dict of logging levels to
format strings, allowing to customise the log records appearance for format strings, allowing to customise the log records appearance for
specific levels. specific levels.
The '*' key identifies the default format string.
Attributes:
fmt: A dictionary mapping logging levels to format strings.
The ``*`` key identifies the default format string.
datefmt: As per py:class:`logging.Formatter`
style: As per py:class:`logging.Formatter`
>>> import sys >>> import sys
>>> handler = logging.StreamHandler(sys.stdout) >>> handler = logging.StreamHandler(sys.stdout)
@ -83,46 +87,48 @@ class LevelFormatter(logging.Formatter):
def configLogger(**kwargs): def configLogger(**kwargs):
""" Do basic configuration for the logging system. This is more or less """A more sophisticated logging system configuation manager.
the same as logging.basicConfig with some additional options and defaults.
The default behaviour is to create a StreamHandler which writes to This is more or less the same as :py:func:`logging.basicConfig`,
sys.stderr, set a formatter using the DEFAULT_FORMATS strings, and add with some additional options and defaults.
The default behaviour is to create a ``StreamHandler`` which writes to
sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
the handler to the top-level library logger ("fontTools"). the handler to the top-level library logger ("fontTools").
A number of optional keyword arguments may be specified, which can alter A number of optional keyword arguments may be specified, which can alter
the default behaviour. the default behaviour.
logger Specifies the logger name or a Logger instance to be configured. Args:
(it defaults to "fontTools" logger). Unlike basicConfig, this
function can be called multiple times to reconfigure a logger. logger: Specifies the logger name or a Logger instance to be
If the logger or any of its children already exists before the configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
call is made, they will be reset before the new configuration this function can be called multiple times to reconfigure a logger.
is applied. If the logger or any of its children already exists before the call is
filename Specifies that a FileHandler be created, using the specified made, they will be reset before the new configuration is applied.
filename, rather than a StreamHandler. filename: Specifies that a ``FileHandler`` be created, using the
filemode Specifies the mode to open the file, if filename is specified specified filename, rather than a ``StreamHandler``.
(if filemode is unspecified, it defaults to 'a'). filemode: Specifies the mode to open the file, if filename is
format Use the specified format string for the handler. This argument specified. (If filemode is unspecified, it defaults to ``a``).
also accepts a dictionary of format strings keyed by level name, format: Use the specified format string for the handler. This
to allow customising the records appearance for specific levels. argument also accepts a dictionary of format strings keyed by
The special '*' key is for 'any other' level. level name, to allow customising the records appearance for
datefmt Use the specified date/time format. specific levels. The special ``'*'`` key is for 'any other' level.
level Set the logger level to the specified level. datefmt: Use the specified date/time format.
stream Use the specified stream to initialize the StreamHandler. Note level: Set the logger level to the specified level.
that this argument is incompatible with 'filename' - if both stream: Use the specified stream to initialize the StreamHandler. Note
are present, 'stream' is ignored. that this argument is incompatible with ``filename`` - if both
handlers If specified, this should be an iterable of already created are present, ``stream`` is ignored.
handlers, which will be added to the logger. Any handler handlers: If specified, this should be an iterable of already created
in the list which does not have a formatter assigned will be handlers, which will be added to the logger. Any handler in the
assigned the formatter created in this function. list which does not have a formatter assigned will be assigned the
filters If specified, this should be an iterable of already created formatter created in this function.
filters, which will be added to the handler(s), if the latter filters: If specified, this should be an iterable of already created
do(es) not already have filters assigned. filters. If the ``handlers`` do not already have filters assigned,
propagate All loggers have a "propagate" attribute initially set to True, these filters will be added to them.
which determines whether to continue searching for handlers up propagate: All loggers have a ``propagate`` attribute which determines
the logging hierarchy. By default, this arguments sets the whether to continue searching for handlers up the logging hierarchy.
"propagate" attribute to False. If not provided, the "propagate" attribute will be set to ``False``.
""" """
# using kwargs to enforce keyword-only arguments in py2. # using kwargs to enforce keyword-only arguments in py2.
handlers = kwargs.pop("handlers", None) handlers = kwargs.pop("handlers", None)
@ -382,9 +388,11 @@ class Timer(object):
class ChannelsFilter(logging.Filter): class ChannelsFilter(logging.Filter):
""" Filter out records emitted from a list of enabled channel names, """Provides a hierarchical filter for log entries based on channel names.
including their children. It works the same as the logging.Filter class,
but allows to specify multiple channel names. Filters out records emitted from a list of enabled channel names,
including their children. It works the same as the ``logging.Filter``
class, but allows the user to specify multiple channel names.
>>> import sys >>> import sys
>>> handler = logging.StreamHandler(sys.stdout) >>> handler = logging.StreamHandler(sys.stdout)
@ -469,10 +477,12 @@ class CapturingLogHandler(logging.Handler):
class LogMixin(object): class LogMixin(object):
""" Mixin class that adds logging functionality to another class. """ Mixin class that adds logging functionality to another class.
You can define a new class that subclasses from LogMixin as well as
You can define a new class that subclasses from ``LogMixin`` as well as
other base classes through multiple inheritance. other base classes through multiple inheritance.
All instances of that class will have a 'log' property that returns All instances of that class will have a ``log`` property that returns
a logging.Logger named after their respective <module>.<class>. a ``logging.Logger`` named after their respective ``<module>.<class>``.
For example: For example:
>>> class BaseClass(object): >>> class BaseClass(object):

View File

@ -17,6 +17,16 @@ def _reverseString(s):
def getMacCreatorAndType(path): def getMacCreatorAndType(path):
"""Returns file creator and file type codes for a path.
Args:
path (str): A file path.
Returns:
A tuple of two :py:class:`fontTools.py23.Tag` objects, the first
representing the file creator and the second representing the
file type.
"""
if xattr is not None: if xattr is not None:
try: try:
finderInfo = xattr.getxattr(path, 'com.apple.FinderInfo') finderInfo = xattr.getxattr(path, 'com.apple.FinderInfo')
@ -40,6 +50,17 @@ def getMacCreatorAndType(path):
def setMacCreatorAndType(path, fileCreator, fileType): def setMacCreatorAndType(path, fileCreator, fileType):
"""Set file creator and file type codes for a path.
Note that if the ``xattr`` module is not installed, no action is
taken but no error is raised.
Args:
path (str): A file path.
fileCreator: A four-character file creator tag.
fileType: A four-character file type tag.
"""
if xattr is not None: if xattr is not None:
from fontTools.misc.textTools import pad from fontTools.misc.textTools import pad
if not all(len(s) == 4 for s in (fileCreator, fileType)): if not all(len(s) == 4 for s in (fileCreator, fileType)):

View File

@ -1,4 +1,3 @@
""" Tools for reading Mac resource forks. """
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
import struct import struct
from fontTools.misc import sstruct from fontTools.misc import sstruct
@ -11,8 +10,25 @@ class ResourceError(Exception):
class ResourceReader(MutableMapping): class ResourceReader(MutableMapping):
"""Reader for Mac OS resource forks.
Parses a resource fork and returns resources according to their type.
If run on OS X, this will open the resource fork in the filesystem.
Otherwise, it will open the file itself and attempt to read it as
though it were a resource fork.
The returned object can be indexed by type and iterated over,
returning in each case a list of py:class:`Resource` objects
representing all the resources of a certain type.
"""
def __init__(self, fileOrPath): def __init__(self, fileOrPath):
"""Open a file
Args:
fileOrPath: Either an object supporting a ``read`` method, an
``os.PathLike`` object, or a string.
"""
self._resources = OrderedDict() self._resources = OrderedDict()
if hasattr(fileOrPath, 'read'): if hasattr(fileOrPath, 'read'):
self.file = fileOrPath self.file = fileOrPath
@ -121,6 +137,7 @@ class ResourceReader(MutableMapping):
@property @property
def types(self): def types(self):
"""A list of the types of resources in the resource fork."""
return list(self._resources.keys()) return list(self._resources.keys())
def countResources(self, resType): def countResources(self, resType):
@ -131,6 +148,7 @@ class ResourceReader(MutableMapping):
return 0 return 0
def getIndices(self, resType): def getIndices(self, resType):
"""Returns a list of indices of resources of a given type."""
numRes = self.countResources(resType) numRes = self.countResources(resType)
if numRes: if numRes:
return list(range(1, numRes+1)) return list(range(1, numRes+1))
@ -167,6 +185,15 @@ class ResourceReader(MutableMapping):
class Resource(object): class Resource(object):
"""Represents a resource stored within a resource fork.
Attributes:
type: resource type.
data: resource data.
id: ID.
name: resource name.
attr: attributes.
"""
def __init__(self, resType=None, resData=None, resID=None, resName=None, def __init__(self, resType=None, resData=None, resID=None, resName=None,
resAttr=None): resAttr=None):

View File

@ -91,8 +91,13 @@ def _encode_base64(data, maxlinelength=76, indent_level=1):
class Data: class Data:
"""Wrapper for binary data returned in place of the built-in bytes type """Represents binary data when ``use_builtin_types=False.``
when loading property list data with use_builtin_types=False.
This class wraps binary data loaded from a plist file when the
``use_builtin_types`` argument to the loading function (:py:func:`fromtree`,
:py:func:`load`, :py:func:`loads`) is false.
The actual binary data is retrieved using the ``data`` attribute.
""" """
def __init__(self, data): def __init__(self, data):
@ -395,6 +400,31 @@ def totree(
pretty_print=True, pretty_print=True,
indent_level=1, indent_level=1,
): ):
"""Convert a value derived from a plist into an XML tree.
Args:
value: Any kind of value to be serialized to XML.
sort_keys: Whether keys of dictionaries should be sorted.
skipkeys (bool): Whether to silently skip non-string dictionary
keys.
use_builtin_types (bool): If true, byte strings will be
encoded in Base-64 and wrapped in a ``data`` tag; if
false, they will be either stored as ASCII strings or an
exception raised if they cannot be decoded as such. Defaults
to ``True`` if not present. Deprecated.
pretty_print (bool): Whether to indent the output.
indent_level (int): Level of indentation when serializing.
Returns: an ``etree`` ``Element`` object.
Raises:
``TypeError``
if non-string dictionary keys are serialized
and ``skipkeys`` is false.
``ValueError``
if non-ASCII binary data is present
and `use_builtin_types` is false.
"""
if use_builtin_types is None: if use_builtin_types is None:
use_builtin_types = USE_BUILTIN_TYPES use_builtin_types = USE_BUILTIN_TYPES
else: else:
@ -410,6 +440,17 @@ def totree(
def fromtree(tree, use_builtin_types=None, dict_type=dict): def fromtree(tree, use_builtin_types=None, dict_type=dict):
"""Convert an XML tree to a plist structure.
Args:
tree: An ``etree`` ``Element``.
use_builtin_types: If True, binary data is deserialized to
bytes strings. If False, it is wrapped in :py:class:`Data`
objects. Defaults to True if not provided. Deprecated.
dict_type: What type to use for dictionaries.
Returns: An object (usually a dictionary).
"""
target = PlistTarget( target = PlistTarget(
use_builtin_types=use_builtin_types, dict_type=dict_type use_builtin_types=use_builtin_types, dict_type=dict_type
) )
@ -429,6 +470,20 @@ def fromtree(tree, use_builtin_types=None, dict_type=dict):
def load(fp, use_builtin_types=None, dict_type=dict): def load(fp, use_builtin_types=None, dict_type=dict):
"""Load a plist file into an object.
Args:
fp: An opened file.
use_builtin_types: If True, binary data is deserialized to
bytes strings. If False, it is wrapped in :py:class:`Data`
objects. Defaults to True if not provided. Deprecated.
dict_type: What type to use for dictionaries.
Returns:
An object (usually a dictionary) representing the top level of
the plist file.
"""
if not hasattr(fp, "read"): if not hasattr(fp, "read"):
raise AttributeError( raise AttributeError(
"'%s' object has no attribute 'read'" % type(fp).__name__ "'%s' object has no attribute 'read'" % type(fp).__name__
@ -447,6 +502,20 @@ def load(fp, use_builtin_types=None, dict_type=dict):
def loads(value, use_builtin_types=None, dict_type=dict): def loads(value, use_builtin_types=None, dict_type=dict):
"""Load a plist file from a string into an object.
Args:
value: A string containing a plist.
use_builtin_types: If True, binary data is deserialized to
bytes strings. If False, it is wrapped in :py:class:`Data`
objects. Defaults to True if not provided. Deprecated.
dict_type: What type to use for dictionaries.
Returns:
An object (usually a dictionary) representing the top level of
the plist file.
"""
fp = BytesIO(value) fp = BytesIO(value)
return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type) return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type)
@ -459,6 +528,30 @@ def dump(
use_builtin_types=None, use_builtin_types=None,
pretty_print=True, pretty_print=True,
): ):
"""Write a Python object to a plist file.
Args:
value: An object to write.
fp: A file opened for writing.
sort_keys (bool): Whether keys of dictionaries should be sorted.
skipkeys (bool): Whether to silently skip non-string dictionary
keys.
use_builtin_types (bool): If true, byte strings will be
encoded in Base-64 and wrapped in a ``data`` tag; if
false, they will be either stored as ASCII strings or an
exception raised if they cannot be represented. Defaults
pretty_print (bool): Whether to indent the output.
indent_level (int): Level of indentation when serializing.
Raises:
``TypeError``
if non-string dictionary keys are serialized
and ``skipkeys`` is false.
``ValueError``
if non-representable binary data is present
and `use_builtin_types` is false.
"""
if not hasattr(fp, "write"): if not hasattr(fp, "write"):
raise AttributeError( raise AttributeError(
"'%s' object has no attribute 'write'" % type(fp).__name__ "'%s' object has no attribute 'write'" % type(fp).__name__
@ -493,6 +586,31 @@ def dumps(
use_builtin_types=None, use_builtin_types=None,
pretty_print=True, pretty_print=True,
): ):
"""Write a Python object to a string in plist format.
Args:
value: An object to write.
sort_keys (bool): Whether keys of dictionaries should be sorted.
skipkeys (bool): Whether to silently skip non-string dictionary
keys.
use_builtin_types (bool): If true, byte strings will be
encoded in Base-64 and wrapped in a ``data`` tag; if
false, they will be either stored as strings or an
exception raised if they cannot be represented. Defaults
pretty_print (bool): Whether to indent the output.
indent_level (int): Level of indentation when serializing.
Returns:
string: A plist representation of the Python object.
Raises:
``TypeError``
if non-string dictionary keys are serialized
and ``skipkeys`` is false.
``ValueError``
if non-representable binary data is present
and `use_builtin_types` is false.
"""
fp = BytesIO() fp = BytesIO()
dump( dump(
value, value,