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
:inherited-members:
:members:
:members: userNameToFileName
:undoc-members:

View File

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
############
loggingTools
############
###################################################################
loggingTools: tools for interfacing with the Python logging package
###################################################################
.. automodule:: fontTools.misc.loggingTools
:inherited-members:
: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
:inherited-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
:inherited-members:
:members:
:undoc-members:
:members: ResourceReader, Resource
:member-order: bysource

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:
http://unifiedfontobject.org/versions/ufo3/conventions/
This module implements the algorithm for converting between a "user name" -
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:
https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py
The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
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
Copyright (c) 2005-2016, The RoboFab Developers:
Erik van Blokland
Tal Leming
Just van Rossum
This code was originally copied from
`ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
- Erik van Blokland
- Tal Leming
- Just van Rossum
"""
from fontTools.misc.py23 import basestring, unicode
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
illegalCharacters += [chr(i) for i in range(1, 32)]
illegalCharacters += [chr(0x7F)]
@ -27,54 +31,69 @@ class NameTranslationError(Exception):
def userNameToFileName(userName, existing=[], prefix="", suffix=""):
"""
existing should be a case-insensitive list
of all existing file names.
"""Converts from a user name to a file name.
>>> userNameToFileName("a") == "a"
True
>>> userNameToFileName("A") == "A_"
True
>>> userNameToFileName("AE") == "A_E_"
True
>>> userNameToFileName("Ae") == "A_e"
True
>>> userNameToFileName("ae") == "ae"
True
>>> userNameToFileName("aE") == "aE_"
True
>>> userNameToFileName("a.alt") == "a.alt"
True
>>> userNameToFileName("A.alt") == "A_.alt"
True
>>> userNameToFileName("A.Alt") == "A_.A_lt"
True
>>> userNameToFileName("A.aLt") == "A_.aL_t"
True
>>> userNameToFileName(u"A.alT") == "A_.alT_"
True
>>> userNameToFileName("T_H") == "T__H_"
True
>>> userNameToFileName("T_h") == "T__h"
True
>>> userNameToFileName("t_h") == "t_h"
True
>>> userNameToFileName("F_F_I") == "F__F__I_"
True
>>> userNameToFileName("f_f_i") == "f_f_i"
True
>>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
True
>>> userNameToFileName(".notdef") == "_notdef"
True
>>> userNameToFileName("con") == "_con"
True
>>> userNameToFileName("CON") == "C_O_N_"
True
>>> userNameToFileName("con.alt") == "_con.alt"
True
>>> userNameToFileName("alt.con") == "alt._con"
True
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"
True
>>> userNameToFileName("A") == "A_"
True
>>> userNameToFileName("AE") == "A_E_"
True
>>> userNameToFileName("Ae") == "A_e"
True
>>> userNameToFileName("ae") == "ae"
True
>>> userNameToFileName("aE") == "aE_"
True
>>> userNameToFileName("a.alt") == "a.alt"
True
>>> userNameToFileName("A.alt") == "A_.alt"
True
>>> userNameToFileName("A.Alt") == "A_.A_lt"
True
>>> userNameToFileName("A.aLt") == "A_.aL_t"
True
>>> userNameToFileName(u"A.alT") == "A_.alT_"
True
>>> userNameToFileName("T_H") == "T__H_"
True
>>> userNameToFileName("T_h") == "T__h"
True
>>> userNameToFileName("t_h") == "t_h"
True
>>> userNameToFileName("F_F_I") == "F__F__I_"
True
>>> userNameToFileName("f_f_i") == "f_f_i"
True
>>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
True
>>> userNameToFileName(".notdef") == "_notdef"
True
>>> userNameToFileName("con") == "_con"
True
>>> userNameToFileName("CON") == "C_O_N_"
True
>>> userNameToFileName("con.alt") == "_con.alt"
True
>>> userNameToFileName("alt.con") == "alt._con"
True
"""
# the incoming name must be a unicode string
if not isinstance(userName, unicode):

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 *
@ -8,6 +24,7 @@ import logging
log = logging.getLogger(__name__)
__all__ = [
"MAX_F2DOT14",
"otRound",
"fixedToFloat",
"floatToFixed",
@ -21,78 +38,126 @@ __all__ = [
]
# the max value that can still fit in an F2Dot14:
# 1.99993896484375
MAX_F2DOT14 = 0x7FFF / (1 << 14)
def otRound(value):
"""Round float value to nearest integer towards +Infinity.
For fractional values of 0.5 and higher, take the next higher integer;
for other fractional values, truncate.
"""Round float value to nearest integer towards ``+Infinity``.
https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview
https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
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.
This function rounds the floating-point value according to this strategy
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))
def fixedToFloat(value, precisionBits):
"""Converts a fixed-point number to a float given the number of
precisionBits. Ie. value / (1 << precisionBits)
precision bits.
>>> import math
>>> f = fixedToFloat(-10139, precisionBits=14)
>>> math.isclose(f, -0.61883544921875)
True
Args:
value (int): Number in fixed-point format.
precisionBits (int): Number of precision bits.
Returns:
Floating point value.
Examples::
>>> import math
>>> f = fixedToFloat(-10139, precisionBits=14)
>>> math.isclose(f, -0.61883544921875)
True
"""
return value / (1 << precisionBits)
def floatToFixed(value, precisionBits):
"""Converts a float to a fixed-point number given the number of
precisionBits. Ie. round(value * (1 << precisionBits)).
precision bits.
>>> floatToFixed(-0.61883544921875, precisionBits=14)
-10139
>>> floatToFixed(-0.61884, precisionBits=14)
-10139
Args:
value (float): Floating point value.
precisionBits (int): Number of precision bits.
Returns:
int: Fixed-point representation.
Examples::
>>> floatToFixed(-0.61883544921875, precisionBits=14)
-10139
>>> floatToFixed(-0.61884, precisionBits=14)
-10139
"""
return otRound(value * (1 << precisionBits))
def floatToFixedToFloat(value, precisionBits):
"""Converts a float to a fixed-point number given the number of
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)).
"""Converts a float to a fixed-point number and back again.
>>> import math
>>> f1 = -0.61884
>>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
>>> f1 != f2
True
>>> math.isclose(f2, -0.61883544921875)
True
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
>>> f1 = -0.61884
>>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
>>> f1 != f2
True
>>> math.isclose(f2, -0.61883544921875)
True
"""
scale = 1 << precisionBits
return otRound(value * scale) / scale
def fixedToStr(value, precisionBits):
"""Converts a fixed-point number with 'precisionBits' number of fractional binary
digits to a string representing a decimal float, choosing the float that has the
shortest decimal representation (the least number of fractional decimal digits).
Eg. to convert a fixed-point number in a 2.14 format, use precisionBits=14:
"""Converts a fixed-point number to a string representing a decimal float.
>>> fixedToStr(-10139, precisionBits=14)
'-0.61884'
This chooses the float that has the shortest decimal representation (the least
number of fractional decimal digits).
This is pretty slow compared to the simple division used in fixedToFloat.
For example, to convert a fixed-point number in a 2.14 format, use
``precisionBits=14``::
>>> fixedToStr(-10139, precisionBits=14)
'-0.61884'
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
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"
@ -118,10 +183,18 @@ def fixedToStr(value, precisionBits):
def strToFixed(string, precisionBits):
"""Convert the string representation of a decimal float number to a fixed-point
number with 'precisionBits' fractional binary digits.
E.g. to convert a float string to a 2.14 fixed-point number:
"""Converts a string representing a decimal float to a 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)
-10139
"""
@ -130,18 +203,31 @@ def strToFixed(string, precisionBits):
def strToFixedToFloat(string, precisionBits):
"""Convert a string to a decimal float, by first converting the float to a
fixed-point number with precisionBits fractional binary digits.
"""Convert a string to a decimal float with fixed-point rounding.
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))).
>>> import math
>>> s = '-0.61884'
>>> bits = 14
>>> f = strToFixedToFloat(s, precisionBits=bits)
>>> math.isclose(f, -0.61883544921875)
True
>>> f == fixedToFloat(floatToFixed(float(s), precisionBits=bits), precisionBits=bits)
True
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
>>> s = '-0.61884'
>>> bits = 14
>>> f = strToFixedToFloat(s, precisionBits=bits)
>>> math.isclose(f, -0.61883544921875)
True
>>> f == fixedToFloat(floatToFixed(float(s), precisionBits=bits), precisionBits=bits)
True
"""
value = float(string)
scale = 1 << precisionBits
@ -149,21 +235,43 @@ def strToFixedToFloat(string, precisionBits):
def floatToFixedToStr(value, precisionBits):
"""Convert float to string using the shortest decimal representation (ie. the least
number of fractional decimal digits) to represent the equivalent fixed-point number
with 'precisionBits' fractional binary digits.
"""Convert float to string with fixed-point rounding.
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.
>>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
'-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))
return fixedToStr(fixed, precisionBits)
def ensureVersionIsLong(value):
"""Ensure a table version is an unsigned long (unsigned short major,
unsigned short minor) instead of a float."""
"""Ensure a table version is an unsigned long.
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:
newValue = floatToFixed(value, 16)
log.warning(
@ -174,7 +282,14 @@ def ensureVersionIsLong(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 = ensureVersionIsLong(value)
return value

View File

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

View File

@ -17,6 +17,16 @@ def _reverseString(s):
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:
try:
finderInfo = xattr.getxattr(path, 'com.apple.FinderInfo')
@ -40,6 +50,17 @@ def getMacCreatorAndType(path):
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:
from fontTools.misc.textTools import pad
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 *
import struct
from fontTools.misc import sstruct
@ -11,8 +10,25 @@ class ResourceError(Exception):
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):
"""Open a file
Args:
fileOrPath: Either an object supporting a ``read`` method, an
``os.PathLike`` object, or a string.
"""
self._resources = OrderedDict()
if hasattr(fileOrPath, 'read'):
self.file = fileOrPath
@ -121,6 +137,7 @@ class ResourceReader(MutableMapping):
@property
def types(self):
"""A list of the types of resources in the resource fork."""
return list(self._resources.keys())
def countResources(self, resType):
@ -131,6 +148,7 @@ class ResourceReader(MutableMapping):
return 0
def getIndices(self, resType):
"""Returns a list of indices of resources of a given type."""
numRes = self.countResources(resType)
if numRes:
return list(range(1, numRes+1))
@ -167,6 +185,15 @@ class ResourceReader(MutableMapping):
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,
resAttr=None):

View File

@ -91,8 +91,13 @@ def _encode_base64(data, maxlinelength=76, indent_level=1):
class Data:
"""Wrapper for binary data returned in place of the built-in bytes type
when loading property list data with use_builtin_types=False.
"""Represents binary data when ``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):
@ -395,6 +400,31 @@ def totree(
pretty_print=True,
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:
use_builtin_types = USE_BUILTIN_TYPES
else:
@ -410,6 +440,17 @@ def totree(
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(
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):
"""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"):
raise AttributeError(
"'%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):
"""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)
return load(fp, use_builtin_types=use_builtin_types, dict_type=dict_type)
@ -459,6 +528,30 @@ def dump(
use_builtin_types=None,
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"):
raise AttributeError(
"'%s' object has no attribute 'write'" % type(fp).__name__
@ -493,6 +586,31 @@ def dumps(
use_builtin_types=None,
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()
dump(
value,