のねのBlog

パソコンの問題や、ソフトウェアの開発で起きた問題など書いていきます。よろしくお願いします^^。

plistlib.dump(obj, fp)

defcon/object/font.py

    def save(
        self,
        path=None,
        formatVersion=None,
        removeUnreferencedImages=False,
        progressBar=None,
        structure=None,
    ):
        """
        Save the font to **path**. If path is None, the path
        from the last save or when the font was first opened
        will be used.

        The UFO will be saved using the format found at ``ufoFormatVersion``.
        This value is either the format version from the exising UFO or
        the format version specified in a previous save. If neither of
        these is available, the UFO will be written as format version 3.
        If you wish to specifiy the format version for saving, pass
        the desired number as the **formatVersion** argument.

        Optionally, the UFO can be purged of unreferenced images
        during this operation. To do this, pass ``True`` as the
        value for the removeUnreferencedImages argument.

        'structure' can be either None, "zip" or "package". If it's None,
        the destination UFO will use the same structure as original, provided
        that is compatible with any previous UFO at the output path.
        If 'structure' is "zip" the UFO will be saved as compressed archive,
        else it is saved as a regular folder or "package".
        """
        isNewFont = self._path is None
        if path is None:
            if isNewFont:
                from defcon.errors import DefconError
                raise DefconError("Can't save new font without a 'path'")
            # saving in-place to the same original path
            path = self._path
            saveAs = False
        else:
            if not isinstance(path, basestring) and not hasattr(path, "__fspath__"):
                raise TypeError(
                    "invalid path: expected string or os.PathLike, found %s"
                    % type(path).__name__
                )
            if isNewFont:
                # saving a new font is always a 'saveAs' operation
                saveAs = True
            else:
                # 'saveAs' if source and destination path are different
                saveAs = not samepath(self._path, path)

        # validate 'structure' argument
        if structure is not None:
            try:
                structure = UFOFileStructure(structure)
            except ValueError:
                from defcon.errors import DefconError
                raise DefconError(
                    "'%s' is not a valid UFOFileStructure; choose between %s"
                    % (structure, tuple(e.value for e in UFOFileStructure))
                )
        elif self._ufoFileStructure is not None:
            # if structure is None, fall back to the same as when first loaded
            structure = self._ufoFileStructure
        else:
            # if both None, default to "package" structure
            structure = UFOFileStructure.PACKAGE

        # if destination is an existing path, ensure matches the desired structure
        isExistingOSPath = os.path.exists(path)
        if isExistingOSPath:
            try:
                with UFOReader(path, validate=True) as reader:
                    existingStructure = reader.fileStructure
            except UFOLibError:
                # destination is an existing file but not a valid UFO, we'll
                # silently overwrite it. Perhaps we should blow up...
                saveAs = True
            if not saveAs and structure and structure is not existingStructure:
                from defcon.errors import DefconError
                raise DefconError(
                    "Can't save font in-place with a different structure; "
                    "expected %s, got %s"
                    % (existingStructure.value, structure.value)
                )

        # sanity checks on layer data before doing anything destructive
        assert self.layers.defaultLayer is not None
        if self.layers.defaultLayer.name != "public.default":
            assert "public.default" not in self.layers.layerOrder
        ## work out the format version
        # if None is given, fallback to the one that
        # came in when the UFO was loaded
        if formatVersion is None and self._ufoFormatVersion is not None:
            formatVersion = self._ufoFormatVersion
        # otherwise fallback to 3
        elif formatVersion is None:
            formatVersion = 3
        # if down-converting in-place or "saving as" to a pre-existing path,
        # we first write to a temporary folder, then move to destination
        overwritePath = None
        if ((not saveAs and formatVersion != self._ufoFormatVersion) or
                (saveAs and isExistingOSPath)):
            saveAs = True
            overwritePath = path
            path = os.path.join(tempfile.mkdtemp(), "temp.ufo")
        try:
            # make a UFOWriter
            try:
                writer = UFOWriter(
                    path,
                    formatVersion=formatVersion,
                    validate=self.ufoLibWriteValidate,
                    structure=structure,
                )
            except UFOLibError:
                if overwritePath is None and isExistingOSPath:
                    logger.exception("Invalid ufo found '%s', the existing ufo "
                                     "will be removed. Save will be handled as "
                                     "save-as.", path)
                    saveAs = True
                    overwritePath = path
                    path = os.path.join(tempfile.mkdtemp(), "temp.ufo")
                    writer = UFOWriter(
                        path,
                        formatVersion=formatVersion,
                        validate=self.ufoLibWriteValidate,
                        structure=structure,
                    )
                else:
                    raise
            # if changing ufo format versions, flag all objects
            # as dirty so that they will be saved
            if self._ufoFormatVersion != formatVersion:
                self.info.dirty = True
                self.groups.dirty = True
                self.kerning.dirty = True
                self.lib.dirty = True
                if formatVersion > 1:
                    self.features.dirty = True
            # set the kerning group remap if necessary
            if formatVersion < 3 and self._kerningGroupConversionRenameMaps is not None:
                writer.setKerningGroupConversionRenameMaps(self._kerningGroupConversionRenameMaps)
            # save the objects
            self._saveInfo(writer=writer, saveAs=saveAs, progressBar=progressBar) <==========ここ
            self._saveGroups(writer=writer, saveAs=saveAs, progressBar=progressBar)
            # Note: the outgoing kerning data has not been validated.
            # Gremlins may be sneaking out through here.
            self._saveKerning(writer=writer, saveAs=saveAs, progressBar=progressBar)
            self._saveLib(writer=writer, saveAs=saveAs, progressBar=progressBar)
            if formatVersion >= 2:
                self._saveFeatures(writer=writer, saveAs=saveAs, progressBar=progressBar)
            if formatVersion >= 3:
                self.saveImages(writer=writer, removeUnreferencedImages=removeUnreferencedImages, saveAs=saveAs, progressBar=progressBar)
                self.saveData(writer=writer, saveAs=saveAs, progressBar=progressBar)
            self.layers.save(writer, saveAs=saveAs, progressBar=progressBar)
            # we must close the writer's filesystem to actually create the zip;
            # Note that calling writer.close() makes all the SubFS instances
            # derived from it unusable
            if writer.fileStructure is UFOFileStructure.ZIP:
                writer.close()
            writer.setModificationTime()
            if overwritePath is not None:
                if os.path.isfile(overwritePath):
                    os.remove(overwritePath)
                elif os.path.isdir(overwritePath):
                    shutil.rmtree(overwritePath)
                shutil.move(path, overwritePath)
        finally:
            # if down converting in place or overwriting, handle the temp
            if overwritePath is not None:
                shutil.rmtree(os.path.dirname(path))
                path = overwritePath
        # done
        self._path = path
        self._ufoFormatVersion = formatVersion
        self._ufoFileStructure = writer.fileStructure
        self.dirty = False
defcon/object/font.py

    def _saveInfo(self, writer, saveAs=False, progressBar=None):
        # info should always be saved
        if progressBar is not None:
            progressBar.update(text="Saving info...", increment=0)
        self.saveInfo(writer) <=====ここ
        self.info.dirty = False
        self._stampInfoDataState(writer)
        if progressBar is not None:
            progressBar.update()
defcon/object/font.py

    def saveInfo(self, writer):
        """
        Save info. This method should not be called externally.
        Subclasses may override this method to implement custom saving behavior.
        """
        writer.writeInfo(self.info, validate=self.info.ufoLibWriteValidate) <===ここ
fontTools/ufolib/__init__.py

    def writeInfo(self, info, validate=None):
        """
      Write info.plist. This method requires an object
      that supports getting attributes that follow the
      fontinfo.plist version 2 specification. Attributes
      will be taken from the given object and written
      into the file.

      ``validate`` will validate the data, by default it is set to the
      class's validate value, can be overridden.
      """
        if validate is None:
            validate = self._validate
        # gather version 3 data
        infoData = {}
        for attr in list(fontInfoAttributesVersion3ValueData.keys()):
            if hasattr(info, attr):
                try:
                    value = getattr(info, attr)
                except AttributeError:
                    raise UFOLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr)
                if value is None:
                    continue
                infoData[attr] = value
        # down convert data if necessary and validate
        if self._formatVersion == 3:
            if validate:
                infoData = validateInfoVersion3Data(infoData)
        elif self._formatVersion == 2:
            infoData = _convertFontInfoDataVersion3ToVersion2(infoData)
            if validate:
                infoData = validateInfoVersion2Data(infoData)
        elif self._formatVersion == 1:
            infoData = _convertFontInfoDataVersion3ToVersion2(infoData)
            if validate:
                infoData = validateInfoVersion2Data(infoData)
            infoData = _convertFontInfoDataVersion2ToVersion1(infoData)
        # write file
        self._writePlist(FONTINFO_FILENAME, infoData) <=====ここ
fontTools/ufolib/__init__.py

class UFOWriter(UFOReader):
    def __init__(
        self,
        path,
        formatVersion=3,
        fileCreator="com.github.fonttools.ufoLib",
        structure=None,
        validate=True,
    ):
        if formatVersion not in supportedUFOFormatVersions:
            raise UFOLibError("Unsupported UFO format (%d)." % formatVersion)

        if hasattr(path, "__fspath__"):  # support os.PathLike objects
            path = path.__fspath__()

        if isinstance(path, str):
            # normalize path by removing trailing or double slashes
            path = os.path.normpath(path)
            havePreviousFile = os.path.exists(path)
            if havePreviousFile:
                # ensure we use the same structure as the destination
                existingStructure = _sniffFileStructure(path)
                if structure is not None:
                    try:
                        structure = UFOFileStructure(structure)
                    except ValueError:
                        raise UFOLibError(
                            "Invalid or unsupported structure: '%s'" % structure
                        )
                    if structure is not existingStructure:
                        raise UFOLibError(
                            "A UFO with a different structure (%s) already exists "
                            "at the given path: '%s'" % (existingStructure, path)
                        )
                else:
                    structure = existingStructure
            else:
                # if not exists, default to 'package' structure
                if structure is None:
                    structure = UFOFileStructure.PACKAGE
                dirName = os.path.dirname(path)
                if dirName and not os.path.isdir(dirName):
                    raise UFOLibError(
                        "Cannot write to '%s': directory does not exist" % path
                    )
            if structure is UFOFileStructure.ZIP:
                if havePreviousFile:
                    # we can't write a zip in-place, so we have to copy its
                    # contents to a temporary location and work from there, then
                    # upon closing UFOWriter we create the final zip file
                    parentFS = fs.tempfs.TempFS()
                    with fs.zipfs.ZipFS(path, encoding="utf-8") as origFS:
                        fs.copy.copy_fs(origFS, parentFS)
                    # if output path is an existing zip, we require that it contains
                    # one, and only one, root directory (with arbitrary name), in turn
                    # containing all the existing UFO contents
                    rootDirs = [
                        p.name for p in parentFS.scandir("/")
                        # exclude macOS metadata contained in zip file
                        if p.is_dir and p.name != "__MACOSX"
                    ]
                    if len(rootDirs) != 1:
                        raise UFOLibError(
                            "Expected exactly 1 root directory, found %d" % len(rootDirs)
                        )
                    else:
                        # 'ClosingSubFS' ensures that the parent filesystem is closed
                        # when its root subdirectory is closed
                        self.fs = parentFS.opendir(
                            rootDirs[0], factory=fs.subfs.ClosingSubFS
                        )
                else:
                    # if the output zip file didn't exist, we create the root folder;
                    # we name it the same as input 'path', but with '.ufo' extension
                    rootDir = os.path.splitext(os.path.basename(path))[0] + ".ufo"
                    parentFS = fs.zipfs.ZipFS(path, write=True, encoding="utf-8")
                    parentFS.makedir(rootDir)
                    self.fs = parentFS.opendir(rootDir, factory=fs.subfs.ClosingSubFS)
            else:
                self.fs = fs.osfs.OSFS(path, create=True)
            self._fileStructure = structure
            self._havePreviousFile = havePreviousFile
            self._shouldClose = True
        elif isinstance(path, fs.base.FS):
            filesystem = path
            try:
                filesystem.check()
            except fs.errors.FilesystemClosed:
                raise UFOLibError("the filesystem '%s' is closed" % path)
            else:
                self.fs = filesystem
            try:
                path = filesystem.getsyspath("/")
            except fs.errors.NoSysPath:
                # network or in-memory FS may not map to the local one
                path = str(filesystem)
            # if passed an FS object, always use 'package' structure
            if structure and structure is not UFOFileStructure.PACKAGE:
                import warnings

                warnings.warn(
                    "The 'structure' argument is not used when input is an FS object",
                    UserWarning,
                    stacklevel=2,
                )
            self._fileStructure = UFOFileStructure.PACKAGE
            # if FS contains a "metainfo.plist", we consider it non-empty
            self._havePreviousFile = filesystem.exists(METAINFO_FILENAME)
            # the user is responsible for closing the FS object
            self._shouldClose = False
        else:
            raise TypeError(
                "Expected a path string or fs object, found %s"
                % type(path).__name__
            )

        # establish some basic stuff
        self._path = fsdecode(path)
        self._formatVersion = formatVersion
        self._fileCreator = fileCreator
        self._downConversionKerningData = None
        self._validate = validate
        # if the file already exists, get the format version.
        # this will be needed for up and down conversion.
        previousFormatVersion = None
        if self._havePreviousFile:
            metaInfo = self._getPlist(METAINFO_FILENAME)
            previousFormatVersion = metaInfo.get("formatVersion")
            try:
                previousFormatVersion = int(previousFormatVersion)
            except (ValueError, TypeError):
                self.fs.close()
                raise UFOLibError("The existing metainfo.plist is not properly formatted.")
            if previousFormatVersion not in supportedUFOFormatVersions:
                self.fs.close()
                raise UFOLibError("Unsupported UFO format (%d)." % formatVersion)
        # catch down conversion
        if previousFormatVersion is not None and previousFormatVersion > formatVersion:
            raise UFOLibError("The UFO located at this path is a higher version (%d) than the version (%d) that is trying to be written. This is not supported." % (previousFormatVersion, formatVersion))
        # handle the layer contents
        self.layerContents = {}
        if previousFormatVersion is not None and previousFormatVersion >= 3:
            # already exists
            self.layerContents = OrderedDict(self._readLayerContents(validate))
        else:
            # previous < 3
            # imply the layer contents
            if self.fs.exists(DEFAULT_GLYPHS_DIRNAME):
                self.layerContents = {DEFAULT_LAYER_NAME : DEFAULT_GLYPHS_DIRNAME}
        # write the new metainfo
        self._writeMetaInfo() <==============ここの中へ
fontTools/ufolib/__init__.py
    # metainfo.plist

    def _writeMetaInfo(self):
        metaInfo = dict(
            creator=self._fileCreator,
            formatVersion=self._formatVersion
        )
        self._writePlist(METAINFO_FILENAME, metaInfo)
fontTools/ufolib/__init__.py

class _UFOBaseIO:
    def getFileModificationTime(self, path):
    def _getPlist(self, fileName, default=None):

    def _writePlist(self, fileName, obj):
        """
      Write a property list to a file relative to the UFO filesystem's root.
      Do this sort of atomically, making it harder to corrupt existing files,
      for example when plistlib encounters an error halfway during write.
      This also checks to see if text matches the text that is already in the
      file at path. If so, the file is not rewritten so that the modification
      date is preserved.
      The errors that could be raised during the writing of a plist are
      unpredictable and/or too large to list, so, a blind try: except: is done.
      If an exception occurs, a UFOLibError will be raised.
      """
        if self._havePreviousFile:
            try:
                data = plistlib.dumps(obj)
            except Exception as e:
                raise UFOLibError(
                    "'%s' could not be written on %s because "
                    "the data is not properly formatted: %s"
                    % (fileName, self.fs, e)
                )
            if self.fs.exists(fileName) and data == self.fs.readbytes(fileName):
                return
            self.fs.writebytes(fileName, data)
        else:
            with self.fs.openbin(fileName, mode="w") as fp:
                try:
                    plistlib.dump(obj, fp) <========ここでエラーになる。
                except Exception as e:
                    raise UFOLibError( 
                        "'%s' could not be written on %s because "
                        "the data is not properly formatted: %s"
                        % (fileName, self.fs, e)
                    )
fontTools/misc/plistlib.py

def dump(
    value,
    fp,
    sort_keys=True,
    skipkeys=False,
    use_builtin_types=None,
    pretty_print=True,
):
    if not hasattr(fp, "write"):
        raise AttributeError(
            "'%s' object has no attribute 'write'" % type(fp).__name__
        )
    root = etree.Element("plist", version="1.0")
    el = totree(
        value,
        sort_keys=sort_keys,
        skipkeys=skipkeys,
        use_builtin_types=use_builtin_types,
        pretty_print=pretty_print,
    )
    root.append(el)
    tree = etree.ElementTree(root)
    # we write the doctype ourselves instead of using the 'doctype' argument
    # of 'write' method, becuse lxml will force adding a '\n' even when
    # pretty_print is False.
    if pretty_print:
        header = b"\n".join((XML_DECLARATION, PLIST_DOCTYPE, b""))
    else:
        header = XML_DECLARATION + PLIST_DOCTYPE
    fp.write(header)
    tree.write(
        fp, encoding="utf-8", pretty_print=pretty_print, xml_declaration=False
    )