From fed7cef3cfaf907aaa3d0cda4c654f77bf5f729e Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 5 Jun 2016 16:08:36 +0100 Subject: [PATCH 01/64] validators: check isinstance of collections.Mapping rather than dict The `collections` module provides a set of abstract base classes that can be used to test whether a class provides a particular interface (e.g. whether it is a mapping). It's better if ufoLib uses that with `isinstance` rather than requiring instances of `dict`, or subclasses of `dict`. --- Lib/ufoLib/validators.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Lib/ufoLib/validators.py b/Lib/ufoLib/validators.py index a2377c91b..b453615d7 100644 --- a/Lib/ufoLib/validators.py +++ b/Lib/ufoLib/validators.py @@ -4,6 +4,11 @@ import os import calendar from io import open +try: + from collections.abc import Mapping # python >= 3.3 +except ImportError: + from collections import Mapping + # ------- # Python 2 or 3 # ------- @@ -21,7 +26,7 @@ def isDictEnough(value): Some objects will likely come in that aren't dicts but are dict-ish enough. """ - if isinstance(value, dict): + if isinstance(value, Mapping): return True attrs = ("keys", "values", "items") for attr in attrs: @@ -75,7 +80,7 @@ def genericDictValidator(value, prototype): Generic. (Added at version 3.) """ # not a dict - if not isinstance(value, dict): + if not isinstance(value, Mapping): return False # missing required keys for key, (typ, required) in list(prototype.items()): @@ -907,12 +912,12 @@ def kerningValidator(data): (False, 'The kerning data is not in the correct format.') """ bogusFormatMessage = "The kerning data is not in the correct format." - if not isinstance(data, dict): + if not isinstance(data, Mapping): return False, bogusFormatMessage for first, secondDict in list(data.items()): if not isinstance(first, basestring): return False, bogusFormatMessage - elif not isinstance(secondDict, dict): + elif not isinstance(secondDict, Mapping): return False, bogusFormatMessage for second, value in list(secondDict.items()): if not isinstance(second, basestring): From f6689faf5bef39dc38883e11148a42ca29ed8c95 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sat, 25 Jun 2016 19:04:24 +0100 Subject: [PATCH 02/64] glifLib: don't raise AttributeError if element.text attribute is None When an element tree is built from code, or it is parsed from a string that was not pretty-printed (i.e. without indentation), the 'text' attribute of an Element instance can be None. So we must ensure it's not None before calling `strip()` method. --- Lib/ufoLib/glifLib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index 15aa07a5f..3dc2f943f 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -808,7 +808,7 @@ def _glifTreeFromFile(aFile): root = ElementTree.parse(aFile).getroot() if root.tag != "glyph": raise GlifLibError("The GLIF is not properly formatted.") - if root.text.strip() != '': + if root.text and root.text.strip() != '': raise GlifLibError("Invalid GLIF structure.") return root @@ -816,7 +816,7 @@ def _glifTreeFromString(aString): root = ElementTree.fromstring(aString) if root.tag != "glyph": raise GlifLibError("The GLIF is not properly formatted.") - if root.text.strip() != '': + if root.text and root.text.strip() != '': raise GlifLibError("Invalid GLIF structure.") return root @@ -852,7 +852,7 @@ def _readGlyphFromTreeFormat1(tree, glyphObject=None, pointPen=None): raise GlifLibError("The outline element occurs more than once.") if element.attrib: raise GlifLibError("The outline element contains unknown attributes.") - if element.text.strip() != '': + if element.text and element.text.strip() != '': raise GlifLibError("Invalid outline structure.") haveSeenOutline = True buildOutlineFormat1(glyphObject, pointPen, element) @@ -902,7 +902,7 @@ def _readGlyphFromTreeFormat2(tree, glyphObject=None, pointPen=None): raise GlifLibError("The outline element occurs more than once.") if element.attrib: raise GlifLibError("The outline element contains unknown attributes.") - if element.text.strip() != '': + if element.text and element.text.strip() != '': raise GlifLibError("Invalid outline structure.") haveSeenOutline = True if pointPen is not None: From f92dcf36c188cbe57c144e0ffb97b6bc8f562e59 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 24 Jun 2016 13:43:42 +0100 Subject: [PATCH 03/64] don't write layerinfo.plist if 'lib' element is empty --- Lib/ufoLib/glifLib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index 3dc2f943f..4efa8314c 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -203,7 +203,7 @@ class GlyphSet(object): value = getattr(info, attr) except AttributeError: raise GlifLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr) - if value is None: + if value is None or (attr == 'lib' and not value): continue infoData[attr] = value # validate From ec0fe57545aa2dbef4b1bc079f5cebb34a5f8cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20T=C3=A9tar?= Date: Fri, 8 Jul 2016 21:17:56 -0700 Subject: [PATCH 04/64] filenames: only allow unicode in py2 i.e. restore the old behavior --- Lib/ufoLib/filenames.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/ufoLib/filenames.py b/Lib/ufoLib/filenames.py index b6e310c9a..4a82e2c36 100644 --- a/Lib/ufoLib/filenames.py +++ b/Lib/ufoLib/filenames.py @@ -2,11 +2,8 @@ User name to file name conversion. This was taken form the UFO 3 spec. """ +from fontTools.misc.py23 import unicode -try: - basestring -except NameError: - basestring = str illegalCharacters = "\" * + / : < > ? [ \ ] | \0".split(" ") illegalCharacters += [chr(i) for i in range(1, 32)] @@ -71,7 +68,7 @@ def userNameToFileName(userName, existing=[], prefix="", suffix=""): u'alt._con' """ # the incoming name must be a unicode string - assert isinstance(userName, basestring), "The value for userName must be a unicode string." + assert isinstance(userName, unicode), "The value for userName must be a unicode string." # establish the prefix and suffix lengths prefixLength = len(prefix) suffixLength = len(suffix) From bb21cb7c3a1568b8e6ebfc92f6704b0b810754d8 Mon Sep 17 00:00:00 2001 From: Denis Moyogo Jacquerye Date: Mon, 11 Jul 2016 08:00:23 +0100 Subject: [PATCH 05/64] userNameToFileName expects userName to be unicode strings, let it raise if not --- Lib/ufoLib/__init__.py | 10 +++------- Lib/ufoLib/filenames.py | 5 +++-- Lib/ufoLib/glifLib.py | 6 +++--- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index f2dfc9d73..3e3723b9e 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -3,6 +3,7 @@ import shutil from io import StringIO, BytesIO, open import codecs from copy import deepcopy +from fontTools.misc.py23 import basestring, unicode from ufoLib.glifLib import GlyphSet from ufoLib.validators import * from ufoLib.filenames import userNameToFileName @@ -38,11 +39,6 @@ fontinfo.plist values between the possible format versions. convertFontInfoValueForAttributeFromVersion3ToVersion2 """ -try: - basestring -except NameError: - basestring = str - __all__ = [ "makeUFOPath" "UFOLibError", @@ -1066,9 +1062,9 @@ class UFOWriter(object): # not caching this could be slightly expensive, # but caching it will be cumbersome existing = [d.lower() for d in list(self.layerContents.values())] - if not isinstance(layerName, basestring): + if not isinstance(layerName, unicode): try: - layerName = str(layerName) + layerName = unicode(layerName) except UnicodeDecodeError: raise UFOLibError("The specified layer name is not a Unicode string.") directory = userNameToFileName(layerName, existing=existing, prefix="glyphs.") diff --git a/Lib/ufoLib/filenames.py b/Lib/ufoLib/filenames.py index 4a82e2c36..9eb17d340 100644 --- a/Lib/ufoLib/filenames.py +++ b/Lib/ufoLib/filenames.py @@ -2,7 +2,7 @@ User name to file name conversion. This was taken form the UFO 3 spec. """ -from fontTools.misc.py23 import unicode +from fontTools.misc.py23 import basestring, unicode illegalCharacters = "\" * + / : < > ? [ \ ] | \0".split(" ") @@ -68,7 +68,8 @@ def userNameToFileName(userName, existing=[], prefix="", suffix=""): u'alt._con' """ # the incoming name must be a unicode string - assert isinstance(userName, unicode), "The value for userName must be a unicode string." + if not isinstance(userName, unicode): + raise ValueError("The value for userName must be a unicode string.") # establish the prefix and suffix lengths prefixLength = len(prefix) suffixLength = len(suffix) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index 4efa8314c..fb350d839 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -15,7 +15,7 @@ from __future__ import unicode_literals import os from io import BytesIO, open from warnings import warn -from fontTools.misc.py23 import tobytes +from fontTools.misc.py23 import tobytes, unicode from ufoLib.plistlib import PlistWriter, readPlist, writePlist from ufoLib.plistFromETree import readPlistFromTree from ufoLib.pointPen import AbstractPointPen, PointToSegmentPen @@ -457,9 +457,9 @@ def glyphNameToFileName(glyphName, glyphSet): existing = [name.lower() for name in list(glyphSet.contents.values())] else: existing = [] - if not isinstance(glyphName, basestring): + if not isinstance(glyphName, unicode): try: - new = str(glyphName) + new = unicode(glyphName) glyphName = new except UnicodeDecodeError: pass From 4f83f4bbe137a7e81bc307b1cf5df8e1ffcb2ccd Mon Sep 17 00:00:00 2001 From: Denis Moyogo Jacquerye Date: Mon, 11 Jul 2016 08:55:10 +0100 Subject: [PATCH 06/64] tests: copy doctest as unittest in test_filenames --- Lib/ufoLib/test/test_filenames.py | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Lib/ufoLib/test/test_filenames.py diff --git a/Lib/ufoLib/test/test_filenames.py b/Lib/ufoLib/test/test_filenames.py new file mode 100644 index 000000000..b57854bf5 --- /dev/null +++ b/Lib/ufoLib/test/test_filenames.py @@ -0,0 +1,98 @@ +from __future__ import unicode_literals +import unittest +from ufoLib.filenames import userNameToFileName, handleClash1, handleClash2 + + +class TestFilenames(unittest.TestCase): + + def test_userNameToFileName(self): + self.assertEqual(userNameToFileName("a"), "a") + self.assertEqual(userNameToFileName("A"), "A_") + self.assertEqual(userNameToFileName("AE"), "A_E_") + self.assertEqual(userNameToFileName("Ae"), "A_e") + self.assertEqual(userNameToFileName("ae"), "ae") + self.assertEqual(userNameToFileName("aE"), "aE_") + self.assertEqual(userNameToFileName("a.alt"), "a.alt") + self.assertEqual(userNameToFileName("A.alt"), "A_.alt") + self.assertEqual(userNameToFileName("A.Alt"), "A_.A_lt") + self.assertEqual(userNameToFileName("A.aLt"), "A_.aL_t") + self.assertEqual(userNameToFileName("A.alT"), "A_.alT_") + self.assertEqual(userNameToFileName("T_H"), "T__H_") + self.assertEqual(userNameToFileName("T_h"), "T__h") + self.assertEqual(userNameToFileName("t_h"), "t_h") + self.assertEqual(userNameToFileName("F_F_I"), "F__F__I_") + self.assertEqual(userNameToFileName("f_f_i"), "f_f_i") + self.assertEqual(userNameToFileName("Aacute_V.swash"), + "A_acute_V_.swash") + self.assertEqual(userNameToFileName(".notdef"), "_notdef") + self.assertEqual(userNameToFileName("con"), "_con") + self.assertEqual(userNameToFileName("CON"), "C_O_N_") + self.assertEqual(userNameToFileName("con.alt"), "_con.alt") + self.assertEqual(userNameToFileName("alt.con"), "alt._con") + + def test_userNameToFileName_ValueError(self): + with self.assertRaises(ValueError): + userNameToFileName(b"a") + with self.assertRaises(ValueError): + userNameToFileName({"a"}) + with self.assertRaises(ValueError): + userNameToFileName(("a",)) + with self.assertRaises(ValueError): + userNameToFileName(["a"]) + with self.assertRaises(ValueError): + userNameToFileName(["a"]) + with self.assertRaises(ValueError): + userNameToFileName(b"\xd8\x00") + + def test_handleClash1(self): + prefix = ("0" * 5) + "." + suffix = "." + ("0" * 10) + existing = ["a" * 5] + + e = list(existing) + self.assertEqual( + handleClash1(userName="A" * 5, existing=e, prefix=prefix, + suffix=suffix), + '00000.AAAAA000000000000001.0000000000' + ) + + e = list(existing) + e.append(prefix + "aaaaa" + "1".zfill(15) + suffix) + self.assertEqual( + handleClash1(userName="A" * 5, existing=e, prefix=prefix, + suffix=suffix), + '00000.AAAAA000000000000002.0000000000' + ) + + e = list(existing) + e.append(prefix + "AAAAA" + "2".zfill(15) + suffix) + self.assertEqual( + handleClash1(userName="A" * 5, existing=e, prefix=prefix, + suffix=suffix), + '00000.AAAAA000000000000001.0000000000' + ) + + def test_handleClash2(self): + prefix = ("0" * 5) + "." + suffix = "." + ("0" * 10) + existing = [prefix + str(i) + suffix for i in range(100)] + + e = list(existing) + self.assertEqual( + handleClash2(existing=e, prefix=prefix, suffix=suffix), + '00000.100.0000000000' + ) + + e = list(existing) + e.remove(prefix + "1" + suffix) + self.assertEqual( + handleClash2(existing=e, prefix=prefix, suffix=suffix), + '00000.1.0000000000' + ) + + e = list(existing) + e.remove(prefix + "2" + suffix) + self.assertEqual( + handleClash2(existing=e, prefix=prefix, suffix=suffix), + '00000.2.0000000000' + ) From 78f7d02d6b814d05f1f8e49fe712fa911635673e Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:05:59 +0100 Subject: [PATCH 07/64] add MANIFEST.in to include extra files in source distribution --- MANIFEST.in | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..464aec90e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include README.md notes.txt LICENSE.txt + +include Documentation/Makefile +recursive-include Documentation *.py *.rst + +recursive-include Data *.plist *.glif +recursive-include TestData *.plist *.glif *.fea *.txt + +include requirements.txt tox.ini From 6f6a10dfa2efe420df448512cab191d7565a4ecc Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:09:11 +0100 Subject: [PATCH 08/64] setup.py: add pytest runner; only use setuptools, drop distutils; set version 2.0.dev1; pep8 whitespace --- setup.py | 89 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/setup.py b/setup.py index bfd33a758..fc4e87687 100755 --- a/setup.py +++ b/setup.py @@ -1,15 +1,6 @@ #! /usr/bin/env python - -import os, sys - -try: - from setuptools import setup - extra_kwargs = { - "test_suite": "ufoLib.test" - } -except ImportError: - from distutils.core import setup - extra_kwargs = {} +import sys +from setuptools import setup, find_packages try: import fontTools @@ -23,35 +14,49 @@ ufoLib reads and writes Unified Font Object (UFO) files. UFO is a file format that stores fonts source files. """ -setup( - name = "ufoLib", - version = "1.2", - description = "A low-level UFO reader and writer.", - author = "Just van Rossum, Tal Leming, Erik van Blokland, others", - author_email = "info@robofab.com", - maintainer = "Just van Rossum, Tal Leming, Erik van Blokland", - maintainer_email = "info@robofab.com", - url = "http://unifiedfontobject.org", - license = "OpenSource, BSD-style", - platforms = ["Any"], - long_description = long_description, +needs_pytest = {'pytest', 'test'}.intersection(sys.argv) +pytest_runner = ['pytest_runner'] if needs_pytest else [] +needs_wheel = {'bdist_wheel'}.intersection(sys.argv) +wheel = ['wheel'] if needs_wheel else [] - packages = [ - "ufoLib", - ], - package_dir = {'': 'Lib'}, - classifiers = [ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Environment :: Other Environment", - "Intended Audience :: Developers", - "Intended Audience :: End Users/Desktop", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - ], - **extra_kwargs - ) +setup_params = dict( + name="ufoLib", + version="2.0.dev1", + description="A low-level UFO reader and writer.", + author="Just van Rossum, Tal Leming, Erik van Blokland, others", + author_email="info@robofab.com", + maintainer="Just van Rossum, Tal Leming, Erik van Blokland", + maintainer_email="info@robofab.com", + url="http://unifiedfontobject.org", + license="OpenSource, BSD-style", + platforms=["Any"], + long_description=long_description, + package_dir={'': 'Lib'}, + packages=find_packages('Lib'), + setup_requires=pytest_runner + wheel, + tests_require=[ + 'pytest>=3.0.2', + ], + install_requires=[ + # TODO(anthrotype): un-comment this once fonttools>=3.1 is on PyPI. + # In the meantime, pip install -r requirements.txt + # "fonttools>=3.1", + ], + classifiers=[ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Graphics :: Graphics Conversion", + ], +) + + +if __name__ == "__main__": + setup(**setup_params) From a14810a036523098b308b2d52fbac611a308d5e1 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:22:49 +0100 Subject: [PATCH 09/64] add requirements.txt file --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..0caca20b2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# TODO(anthrotype): replace it with 'fonttools>=3.1' once it's on PyPI +https://github.com/behdad/fonttools/archive/7930106740f6f6f25583836c56c6cc593762e779.zip From 730a673c3a3240bb5ec6eb31c6e235b20bf69c2c Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:24:03 +0100 Subject: [PATCH 10/64] setup.cfg: set universal wheel; use zip as sdist format; add test=pytest alias; add default pytest config --- setup.cfg | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..290f61100 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,20 @@ +[wheel] +universal = 1 + +[sdist] +formats = zip + +[aliases] +test = pytest + +[tool:pytest] +minversion = 3.0.2 +testpaths = + Lib/ufoLib +addopts = + # run py.test in verbose mode + -v + # show extra test summary info + -r a + # run doctests in all .py modules + --doctest-modules From 5fb9a1395726a5f6c3026c33ef08886ef0d55d20 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 15:50:02 +0100 Subject: [PATCH 11/64] converters.py: fix up kerningValidator doctest --- Lib/ufoLib/converters.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Lib/ufoLib/converters.py b/Lib/ufoLib/converters.py index be8ee7772..81b2a2586 100644 --- a/Lib/ufoLib/converters.py +++ b/Lib/ufoLib/converters.py @@ -253,15 +253,10 @@ def test(): ... } >>> groups == expected True - - >>> kerningDict = {} - >>> for first, seconds in kerning.items(): - ... for s, value in seconds.items(): - ... key = (first, s) - ... kerningDict[key] = value - >>> from validators import kerningValidator - >>> kerningValidator(kerningDict, groups) - (True, []) + + >>> from .validators import kerningValidator + >>> kerningValidator(kerning) + (True, None) Mixture of known prefixes and groups without prefixes. From ee384469820c281513b26e4def62fa4269980a0d Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:47:39 +0100 Subject: [PATCH 12/64] glifLib.py: use +IGNORE_EXCEPTION_DETAIL to make doctest work on both Python2 and 3 http://python3porting.com/problems.html#handling-expected-exceptions --- Lib/ufoLib/glifLib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index fb350d839..c0303eced 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -1291,7 +1291,7 @@ def _number(s): 1 >>> _number("1.0") 1.0 - >>> _number("a") + >>> _number("a") # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... GlifLibError: Could not convert a to an int or float. From 8747da76a4158851e2ff1b15b868df2b2a616dbd Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:48:40 +0100 Subject: [PATCH 13/64] filenames.py: fix doctests for py2.py3; import unicode_literals --- Lib/ufoLib/filenames.py | 119 +++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/Lib/ufoLib/filenames.py b/Lib/ufoLib/filenames.py index 9eb17d340..876f6b389 100644 --- a/Lib/ufoLib/filenames.py +++ b/Lib/ufoLib/filenames.py @@ -2,6 +2,7 @@ User name to file name conversion. This was taken form the UFO 3 spec. """ +from __future__ import unicode_literals from fontTools.misc.py23 import basestring, unicode @@ -22,50 +23,50 @@ def userNameToFileName(userName, existing=[], prefix="", suffix=""): existing should be a case-insensitive list of all existing file names. - >>> userNameToFileName(u"a") - u'a' - >>> userNameToFileName(u"A") - u'A_' - >>> userNameToFileName(u"AE") - u'A_E_' - >>> userNameToFileName(u"Ae") - u'A_e' - >>> userNameToFileName(u"ae") - u'ae' - >>> userNameToFileName(u"aE") - u'aE_' - >>> userNameToFileName(u"a.alt") - u'a.alt' - >>> userNameToFileName(u"A.alt") - u'A_.alt' - >>> userNameToFileName(u"A.Alt") - u'A_.A_lt' - >>> userNameToFileName(u"A.aLt") - u'A_.aL_t' - >>> userNameToFileName(u"A.alT") - u'A_.alT_' - >>> userNameToFileName(u"T_H") - u'T__H_' - >>> userNameToFileName(u"T_h") - u'T__h' - >>> userNameToFileName(u"t_h") - u't_h' - >>> userNameToFileName(u"F_F_I") - u'F__F__I_' - >>> userNameToFileName(u"f_f_i") - u'f_f_i' - >>> userNameToFileName(u"Aacute_V.swash") - u'A_acute_V_.swash' - >>> userNameToFileName(u".notdef") - u'_notdef' - >>> userNameToFileName(u"con") - u'_con' - >>> userNameToFileName(u"CON") - u'C_O_N_' - >>> userNameToFileName(u"con.alt") - u'_con.alt' - >>> userNameToFileName(u"alt.con") - u'alt._con' + >>> 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): @@ -116,20 +117,23 @@ def handleClash1(userName, existing=[], prefix="", suffix=""): >>> e = list(existing) >>> handleClash1(userName="A" * 5, existing=e, - ... prefix=prefix, suffix=suffix) - '00000.AAAAA000000000000001.0000000000' + ... prefix=prefix, suffix=suffix) == ( + ... '00000.AAAAA000000000000001.0000000000') + True >>> e = list(existing) >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix) >>> handleClash1(userName="A" * 5, existing=e, - ... prefix=prefix, suffix=suffix) - '00000.AAAAA000000000000002.0000000000' + ... prefix=prefix, suffix=suffix) == ( + ... '00000.AAAAA000000000000002.0000000000') + True >>> e = list(existing) >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix) >>> handleClash1(userName="A" * 5, existing=e, - ... prefix=prefix, suffix=suffix) - '00000.AAAAA000000000000001.0000000000' + ... prefix=prefix, suffix=suffix) == ( + ... '00000.AAAAA000000000000001.0000000000') + True """ # if the prefix length + user name length + suffix length + 15 is at # or past the maximum length, silce 15 characters off of the user name @@ -168,18 +172,21 @@ def handleClash2(existing=[], prefix="", suffix=""): >>> existing = [prefix + str(i) + suffix for i in range(100)] >>> e = list(existing) - >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) - '00000.100.0000000000' + >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == ( + ... '00000.100.0000000000') + True >>> e = list(existing) >>> e.remove(prefix + "1" + suffix) - >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) - '00000.1.0000000000' + >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == ( + ... '00000.1.0000000000') + True >>> e = list(existing) >>> e.remove(prefix + "2" + suffix) - >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) - '00000.2.0000000000' + >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == ( + ... '00000.2.0000000000') + True """ # calculate the longest possible string maxLength = maxFileNameLength - len(prefix) - len(suffix) From 908cff36ad88db4a39238d939bdf260467e8a2f0 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:50:58 +0100 Subject: [PATCH 14/64] add tox.ini configuration file install package and run tests in isolated virtual environments --- tox.ini | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..f24aae8d9 --- /dev/null +++ b/tox.ini @@ -0,0 +1,15 @@ +[tox] +envlist = py27, py35 + +[testenv] +basepython = + # we use TOXPYTHON env variable to specify the location of Appveyor Python + py27: {env:TOXPYTHON:python2.7} + py34: {env:TOXPYTHON:python3.4} + py35: {env:TOXPYTHON:python3.5} +deps = + pytest + -rrequirements.txt +commands = + # pass to pytest any extra positional arguments after `tox -- ...` + pytest {posargs} From b497dce200de638ca7035ee3d2633b3db06f250e Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 15:34:35 +0100 Subject: [PATCH 15/64] .travis.yml: run tests with pytest inside tox environment; sudo is not required, use the Travis container-based infrastructure (faster) --- .travis.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 84cba53c7..4164f06fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,11 @@ language: python -sudo: required +sudo: false python: - "2.7" - "3.4" - "3.5" -before_install: - - git clone --depth=1 https://github.com/behdad/fonttools.git install: - - cd fonttools; python setup.py install; cd .. - - python setup.py install -script: - - python -c "import fontTools" - - python setup.py test + - pip install --upgrade pip setuptools wheel + - pip install tox-travis +script: tox + From 6a0790a64557f606bc361623f0ac4602eb1dd1df Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 16:58:51 +0100 Subject: [PATCH 16/64] .travis.yml: add (commented-out) 'deploy') section for Github and PyPI someone with push access must encrypt the Github api token and the PyPI password --- .travis.yml | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4164f06fb..274b9e543 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,39 @@ install: - pip install --upgrade pip setuptools wheel - pip install tox-travis script: tox - +# after_success: +# - pip wheel --no-deps -w dist . +# - python setup.py sdist +# before_deploy: +# - export ZIP=$(ls dist/ufoLib*.zip) +# - export WHL=$(ls dist/ufoLib*.whl) +# +## Replace the encrypted Github api key and PyPI password to enable automatic +## deployment of wheel and sdist to Github Releases and PyPI on tags. +# +# deploy: +# # deploy to Github Releases on tags +# - provider: releases +# api_key: +# secure: H2rz0E/GjRrRmvf6EYWv7Fyu+y4qU+InGiYlQ6xoyrjmMFCTYiPNW0ysu8893JFILusfO48zzEix7HcumQXtJU16XYwqft9eiWIGzSlP1krusmpQ0yTnuy9wEQcd7fXLEQDfiVZpJxWpC1TtsuIdDqpSUFcHDg4hlvn1IIh+6HwRP6+M5Mprze087ydBCKkO4CvqR4olC7bkSPorRtqUsiUHlUxYFS+R75a8ghqVQbky7ew9/r90zljCBFwYTPEv9tQ7OxnAQiUaAYojceThsBrNP1lH83tOoqB2FkKr9I5dxi5gqNuUsJHh3QK0V59oQCxU+iVFYfrlIZHTBQ+2caxIeWm2k9RkMXXkbg1LyfpORt+5L+NcFNE4WT0DTlYrKnpW+jrak0LEWaI97+15uHahph3vOJgaZrTFvRQZUJTWCNcji2jGbZg+O3pS1Vtd1xEgJ1TcrkOYLYBMrVOywbuQL4sXofLxH3I5RwAenkgm5TwxtuiNxpUkqlEhzJ+GYwWtfb7/qtcLIK7Xn6cDdsQG6XYQMVt+HpCKgRLlrQC8si6l+jZBEtJ7o4IKeYbtybFCe5H3V0jnRFRyncPPdpBavqJ093Pr7fGLvmZuHl551WRtyPlBN4aMGsqvqUSjwr/TxsX5zZ1tQ4QTN2YTmCdeUO6P/fwTBf5p0F3QFo8= +# file: +# - "${ZIP}" +# - "${WHL}" +# skip_cleanup: true +# on: +# repo: unified-font-object/ufoLib +# tags: true +# all_branches: true +# python: 3.5 +# # deploy to PyPI on tags +# - provider: pypi +# server: https://upload.pypi.org/legacy/ +# on: +# repo: unified-font-object/ufoLib +# tags: true +# all_branches: true +# python: 3.5 +# user: anthrotype +# password: +# secure: B6rFOTA8ol1kt210q7USDVOzLFWUyMIwdZuaUdejYmUb0CXK6W8kKj0H01yBCgjkhtew9YEQSB62jFiGUOJH9L8JjpJO2RG8cHIIblXQfQ5DXogmcEtsAFotQz0o3UO/SYJaMY917XsPCqw3MTgksd2NWwO7H3YCjaWntN4zZxyTWowGr2Vhbj9s8e+MGMtrZ3Is91AcoXNI7HPj2oVhAtPP5GGpceIP7JlOGdHGUFJKuwJLRKVjEURWXN6ZOQNoxuG6KdkpGtJYCFgRCW65e48WpQs8lts/ByyOMyRItPIPI+K/cPOaIvZ7IUhu9SueBgKcW2YVzQrPzD07N5Lhb4LaKhqgMwpy7oWntnpO0/Xes7bonAoDYKPz7O3O3zl57fy1sVhGXZZNoGUfwyFz1PPecONavRpDQYCMEOq8BcENT86mf0xbAvtmTvBgos8NknmZ35ckUkyrye8t058CwW0yhEO2Pr36XPKoEP9EAiq2YRvINPYnI1T3jTPUUwnc3IAv4zQvPUKWqs2/hEVy8FTCz/0hBgDv2cs4pRcofTzfXSczGMfyuNS3i4n3CHc4FEOYP9MuEWGDC7KF4MVNsBWbh5pkISwhIwNbW1UDept2k+Dmb9XrSoCt5aSvEPJVT3YTA4qTGSUQvhzj1SvXi3s2lh+1WbibEJqNfpdobGg= +# distributions: sdist bdist_wheel From 8db42c8e3778e953b3a8d8645a8d594f31743a7a Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 17:06:39 +0100 Subject: [PATCH 17/64] appveyor.yml: run tests with pytest inside tox environment --- appveyor.yml | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index af00b2c5f..7866cc5ee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,26 +4,38 @@ environment: - PYTHON: "C:\\Python27" PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "32" + TOXENV: "py27" + TOXPYTHON: "C:\\Python27\\python.exe" - PYTHON: "C:\\Python34" - PYTHON_VERSION: "3.4.0" + PYTHON_VERSION: "3.4.x" PYTHON_ARCH: "32" + TOXENV: "py34" + TOXPYTHON: "C:\\Python34\\python.exe" - PYTHON: "C:\\Python35" - PYTHON_VERSION: "3.5.0" + PYTHON_VERSION: "3.5.x" PYTHON_ARCH: "32" + TOXENV: "py35" + TOXPYTHON: "C:\\Python35\\python.exe" - PYTHON: "C:\\Python27-x64" PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "64" + TOXENV: "py27" + TOXPYTHON: "C:\\Python27-x64\\python.exe" - PYTHON: "C:\\Python34-x64" PYTHON_VERSION: "3.4.x" PYTHON_ARCH: "64" + TOXENV: "py34" + TOXPYTHON: "C:\\Python34-x64\\python.exe" - PYTHON: "C:\\Python35-x64" PYTHON_VERSION: "3.5.x" PYTHON_ARCH: "64" + TOXENV: "py35" + TOXPYTHON: "C:\\Python35-x64\\python.exe" init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" @@ -43,24 +55,13 @@ install: # upgrade pip to avoid out-of-date warnings - "pip install --disable-pip-version-check --user --upgrade pip" - # install wheel to build compiled packages - # - "pip install --upgrade wheel" + # install/upgrade setuptools and wheel to build packages + - "pip install --upgrade setuptools wheel" - # install requirements - - "pip install git+https://github.com/behdad/fonttools.git" - - # install - - "python setup.py install" + # install tox to run test suite in a virtual environment + - "pip install -U tox" build: false test_script: - - "python setup.py test" - -# after_test: -# # if tests are successful, create binary packages for the project -# - "pip wheel -w dist ." - -# artifacts: -# # archive the generated packages in the ci.appveyor.com build report -# - path: dist\* + - "tox" From f320554077ddbf5064665f2b39c211e2848f2458 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Sun, 11 Sep 2016 17:44:12 +0100 Subject: [PATCH 18/64] ufoLib.__init__: use os.path instead of slash to fix doctest failing on Windows --- Lib/ufoLib/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index 3e3723b9e..9b2ec400d 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -1176,10 +1176,12 @@ def makeUFOPath(path): """ Return a .ufo pathname. - >>> makeUFOPath("/directory/something.ext") - '/directory/something.ufo' - >>> makeUFOPath("/directory/something.another.thing.ext") - '/directory/something.another.thing.ufo' + >>> makeUFOPath("directory/something.ext") == ( + ... os.path.join('directory', 'something.ufo')) + True + >>> makeUFOPath("directory/something.another.thing.ext") == ( + ... os.path.join('directory', 'something.another.thing.ufo')) + True """ dir, name = os.path.split(path) name = ".".join([".".join(name.split(".")[:-1]), "ufo"]) From e6a1a658f01888a0b37280e3dfca50dbfc16b96b Mon Sep 17 00:00:00 2001 From: Denis Moyogo Jacquerye Date: Thu, 13 Oct 2016 21:40:29 +0100 Subject: [PATCH 19/64] glifLib: try importing from fontTools.misc.xmlWriter first fixes #41 --- Lib/ufoLib/glifLib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index c0303eced..af1ee688e 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -533,10 +533,10 @@ def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, writer= """ if writer is None: try: - from xmlWriter import XMLWriter + from fontTools.misc.xmlWriter import XMLWriter except ImportError: # try the other location - from fontTools.misc.xmlWriter import XMLWriter + from xmlWriter import XMLWriter aFile = BytesIO() writer = XMLWriter(aFile, encoding="UTF-8") else: From e7aa1768c043e4b67b78f791aa489dacfe88c421 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 17 Oct 2016 15:19:59 +0100 Subject: [PATCH 20/64] specify fonttools 3.1.2 from PyPI in both setup.py and requirements.txt --- requirements.txt | 3 +-- setup.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0caca20b2..2a2e55f66 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -# TODO(anthrotype): replace it with 'fonttools>=3.1' once it's on PyPI -https://github.com/behdad/fonttools/archive/7930106740f6f6f25583836c56c6cc593762e779.zip +fonttools==3.1.2 diff --git a/setup.py b/setup.py index fc4e87687..b9b094d6d 100755 --- a/setup.py +++ b/setup.py @@ -38,9 +38,7 @@ setup_params = dict( 'pytest>=3.0.2', ], install_requires=[ - # TODO(anthrotype): un-comment this once fonttools>=3.1 is on PyPI. - # In the meantime, pip install -r requirements.txt - # "fonttools>=3.1", + "fonttools>=3.1.2", ], classifiers=[ "Development Status :: 4 - Beta", From 6bfe462ba73a26ff5b37f597ff598d0ec638a138 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 17 Oct 2016 16:18:57 +0100 Subject: [PATCH 21/64] setup.py: point package url to github repo; remove `import fontTools` check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `url` field in setup.py must be the home page for the package. I moved the url to the UFO specĀ in the 'long_description', so it will show up in the PyPI project page. Checking for `import fontTools` is redundant since dependency resolution is now automatic. --- setup.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index b9b094d6d..540397c59 100755 --- a/setup.py +++ b/setup.py @@ -2,16 +2,12 @@ import sys from setuptools import setup, find_packages -try: - import fontTools -except ImportError: - print("*** Warning: ufoLib needs FontTools for some operations, see:") - print(" https://github.com/behdad/fonttools") - long_description = """\ -ufoLib reads and writes Unified Font Object (UFO) files. UFO is a file format -that stores fonts source files. +ufoLib reads and writes Unified Font Object (UFO) files. +UFO is a file format that stores fonts source files. + +http://unifiedfontobject.org """ needs_pytest = {'pytest', 'test'}.intersection(sys.argv) @@ -27,7 +23,7 @@ setup_params = dict( author_email="info@robofab.com", maintainer="Just van Rossum, Tal Leming, Erik van Blokland", maintainer_email="info@robofab.com", - url="http://unifiedfontobject.org", + url="https://github.com/unified-font-object/ufoLib", license="OpenSource, BSD-style", platforms=["Any"], long_description=long_description, From 3b9ced71e06310dd7320087d954ceb1ed85ae01c Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 17 Oct 2016 19:37:52 +0100 Subject: [PATCH 22/64] move all test data inside the inlined ufoLib/test modules, and install them Since the test suite is inlined, it makes sense to also have the test data installed alongside the test modules. So all the content of the two top-level folders Data and TestData is now placed inside Lib/ufoLib/test/testadata. The MANIFEST.in has been adjusted accordingly. The `install_package_data` in setup.py ensures the package data specified in the manifest is installed with the package. This way anyone who installs ufoLib (even the wheel from PyPI) can run the test suite with `pytest --pyargs ufoLib`. --- Lib/ufoLib/test/testSupport.py | 5 ++--- Lib/ufoLib/test/test_UFO3.py | 8 ++------ Lib/ufoLib/test/test_UFOConversion.py | 8 ++------ .../ufoLib/test/testdata}/DemoFont.ufo/fontinfo.plist | 0 .../ufoLib/test/testdata}/DemoFont.ufo/glyphs/A_.glif | 0 .../ufoLib/test/testdata}/DemoFont.ufo/glyphs/B_.glif | 0 .../ufoLib/test/testdata}/DemoFont.ufo/glyphs/F_.glif | 0 .../test/testdata}/DemoFont.ufo/glyphs/F__A__B_.glif | 0 .../ufoLib/test/testdata}/DemoFont.ufo/glyphs/G_.glif | 0 .../ufoLib/test/testdata}/DemoFont.ufo/glyphs/O_.glif | 0 .../ufoLib/test/testdata}/DemoFont.ufo/glyphs/R_.glif | 0 .../testdata}/DemoFont.ufo/glyphs/a#condensed_bold.glif | 0 .../testdata}/DemoFont.ufo/glyphs/a#condensed_light.glif | 0 .../test/testdata}/DemoFont.ufo/glyphs/a#wide_bold.glif | 0 .../test/testdata}/DemoFont.ufo/glyphs/a#wide_light.glif | 0 .../ufoLib/test/testdata}/DemoFont.ufo/glyphs/a.glif | 0 .../test/testdata}/DemoFont.ufo/glyphs/contents.plist | 0 .../test/testdata}/DemoFont.ufo/glyphs/testglyph1.glif | 0 .../DemoFont.ufo/glyphs/testglyph1.reversed.glif | 0 .../ufoLib/test/testdata}/DemoFont.ufo/metainfo.plist | 0 .../test/testdata}/TestFont1 (UFO1).ufo/fontinfo.plist | 0 .../test/testdata}/TestFont1 (UFO1).ufo/glyphs/A_.glif | 0 .../test/testdata}/TestFont1 (UFO1).ufo/glyphs/B_.glif | 0 .../testdata}/TestFont1 (UFO1).ufo/glyphs/contents.plist | 0 .../test/testdata}/TestFont1 (UFO1).ufo/groups.plist | 0 .../test/testdata}/TestFont1 (UFO1).ufo/kerning.plist | 0 .../ufoLib/test/testdata}/TestFont1 (UFO1).ufo/lib.plist | 0 .../test/testdata}/TestFont1 (UFO1).ufo/metainfo.plist | 0 .../test/testdata}/TestFont1 (UFO2).ufo/features.fea | 0 .../test/testdata}/TestFont1 (UFO2).ufo/fontinfo.plist | 0 .../test/testdata}/TestFont1 (UFO2).ufo/glyphs/A_.glif | 0 .../test/testdata}/TestFont1 (UFO2).ufo/glyphs/B_.glif | 0 .../testdata}/TestFont1 (UFO2).ufo/glyphs/contents.plist | 0 .../test/testdata}/TestFont1 (UFO2).ufo/groups.plist | 0 .../test/testdata}/TestFont1 (UFO2).ufo/kerning.plist | 0 .../ufoLib/test/testdata}/TestFont1 (UFO2).ufo/lib.plist | 0 .../test/testdata}/TestFont1 (UFO2).ufo/metainfo.plist | 0 .../data/org.unifiedfontobject.directory/bar/lol.txt | 0 .../data/org.unifiedfontobject.directory/foo.txt | 0 .../data/org.unifiedfontobject.file.txt | 0 .../test/testdata}/UFO3-Read Data.ufo/metainfo.plist | 0 MANIFEST.in | 3 +-- setup.py | 1 + tox.ini | 5 +++-- 44 files changed, 11 insertions(+), 19 deletions(-) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/fontinfo.plist (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/A_.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/B_.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/F_.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/F__A__B_.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/G_.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/O_.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/R_.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/a#condensed_bold.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/a#condensed_light.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/a#wide_bold.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/a#wide_light.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/a.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/contents.plist (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/testglyph1.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/glyphs/testglyph1.reversed.glif (100%) rename {Data => Lib/ufoLib/test/testdata}/DemoFont.ufo/metainfo.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/fontinfo.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/glyphs/A_.glif (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/glyphs/B_.glif (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/glyphs/contents.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/groups.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/kerning.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/lib.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO1).ufo/metainfo.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/features.fea (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/fontinfo.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/glyphs/A_.glif (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/glyphs/B_.glif (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/glyphs/contents.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/groups.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/kerning.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/lib.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/TestFont1 (UFO2).ufo/metainfo.plist (100%) rename {TestData => Lib/ufoLib/test/testdata}/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/bar/lol.txt (100%) rename {TestData => Lib/ufoLib/test/testdata}/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/foo.txt (100%) rename {TestData => Lib/ufoLib/test/testdata}/UFO3-Read Data.ufo/data/org.unifiedfontobject.file.txt (100%) rename {TestData => Lib/ufoLib/test/testdata}/UFO3-Read Data.ufo/metainfo.plist (100%) diff --git a/Lib/ufoLib/test/testSupport.py b/Lib/ufoLib/test/testSupport.py index bd364c8d6..2f02cd1e7 100755 --- a/Lib/ufoLib/test/testSupport.py +++ b/Lib/ufoLib/test/testSupport.py @@ -12,9 +12,8 @@ except NameError: def getDemoFontPath(): """Return the path to Data/DemoFont.ufo/.""" - import ufoLib - root = os.path.dirname(os.path.dirname(os.path.dirname(ufoLib.__file__))) - return os.path.join(root, "Data", "DemoFont.ufo") + testdata = os.path.join(os.path.dirname(__file__), "testdata") + return os.path.join(testdata, "DemoFont.ufo") def getDemoFontGlyphSetPath(): diff --git a/Lib/ufoLib/test/test_UFO3.py b/Lib/ufoLib/test/test_UFO3.py index 7a92c480b..8161e9cbe 100644 --- a/Lib/ufoLib/test/test_UFO3.py +++ b/Lib/ufoLib/test/test_UFO3.py @@ -4139,12 +4139,8 @@ class UFO3WriteLayersTestCase(unittest.TestCase): class UFO3ReadDataTestCase(unittest.TestCase): def getFontPath(self): - import ufoLib - path = os.path.dirname(ufoLib.__file__) - path = os.path.dirname(path) - path = os.path.dirname(path) - path = os.path.join(path, "TestData", "UFO3-Read Data.ufo") - return path + testdata = os.path.join(os.path.dirname(__file__), "testdata") + return os.path.join(testdata, "UFO3-Read Data.ufo") def testUFOReaderDataDirectoryListing(self): reader = UFOReader(self.getFontPath()) diff --git a/Lib/ufoLib/test/test_UFOConversion.py b/Lib/ufoLib/test/test_UFOConversion.py index 16e1de16d..cd7a36afe 100644 --- a/Lib/ufoLib/test/test_UFOConversion.py +++ b/Lib/ufoLib/test/test_UFOConversion.py @@ -30,12 +30,8 @@ class ConversionFunctionsTestCase(unittest.TestCase): shutil.rmtree(path) def getFontPath(self, fileName): - import ufoLib - path = os.path.dirname(ufoLib.__file__) - path = os.path.dirname(path) - path = os.path.dirname(path) - path = os.path.join(path, "TestData", fileName) - return path + testdata = os.path.join(os.path.dirname(__file__), "testdata") + return os.path.join(testdata, fileName) def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures): # result diff --git a/Data/DemoFont.ufo/fontinfo.plist b/Lib/ufoLib/test/testdata/DemoFont.ufo/fontinfo.plist similarity index 100% rename from Data/DemoFont.ufo/fontinfo.plist rename to Lib/ufoLib/test/testdata/DemoFont.ufo/fontinfo.plist diff --git a/Data/DemoFont.ufo/glyphs/A_.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/A_.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/A_.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/A_.glif diff --git a/Data/DemoFont.ufo/glyphs/B_.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/B_.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/B_.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/B_.glif diff --git a/Data/DemoFont.ufo/glyphs/F_.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/F_.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/F_.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/F_.glif diff --git a/Data/DemoFont.ufo/glyphs/F__A__B_.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/F__A__B_.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/F__A__B_.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/F__A__B_.glif diff --git a/Data/DemoFont.ufo/glyphs/G_.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/G_.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/G_.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/G_.glif diff --git a/Data/DemoFont.ufo/glyphs/O_.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/O_.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/O_.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/O_.glif diff --git a/Data/DemoFont.ufo/glyphs/R_.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/R_.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/R_.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/R_.glif diff --git a/Data/DemoFont.ufo/glyphs/a#condensed_bold.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#condensed_bold.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/a#condensed_bold.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#condensed_bold.glif diff --git a/Data/DemoFont.ufo/glyphs/a#condensed_light.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#condensed_light.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/a#condensed_light.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#condensed_light.glif diff --git a/Data/DemoFont.ufo/glyphs/a#wide_bold.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#wide_bold.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/a#wide_bold.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#wide_bold.glif diff --git a/Data/DemoFont.ufo/glyphs/a#wide_light.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#wide_light.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/a#wide_light.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a#wide_light.glif diff --git a/Data/DemoFont.ufo/glyphs/a.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/a.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/a.glif diff --git a/Data/DemoFont.ufo/glyphs/contents.plist b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/contents.plist similarity index 100% rename from Data/DemoFont.ufo/glyphs/contents.plist rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/contents.plist diff --git a/Data/DemoFont.ufo/glyphs/testglyph1.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/testglyph1.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/testglyph1.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/testglyph1.glif diff --git a/Data/DemoFont.ufo/glyphs/testglyph1.reversed.glif b/Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/testglyph1.reversed.glif similarity index 100% rename from Data/DemoFont.ufo/glyphs/testglyph1.reversed.glif rename to Lib/ufoLib/test/testdata/DemoFont.ufo/glyphs/testglyph1.reversed.glif diff --git a/Data/DemoFont.ufo/metainfo.plist b/Lib/ufoLib/test/testdata/DemoFont.ufo/metainfo.plist similarity index 100% rename from Data/DemoFont.ufo/metainfo.plist rename to Lib/ufoLib/test/testdata/DemoFont.ufo/metainfo.plist diff --git a/TestData/TestFont1 (UFO1).ufo/fontinfo.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/fontinfo.plist similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/fontinfo.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/fontinfo.plist diff --git a/TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/glyphs/A_.glif similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/glyphs/A_.glif diff --git a/TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/glyphs/B_.glif similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/glyphs/B_.glif diff --git a/TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/glyphs/contents.plist similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/glyphs/contents.plist diff --git a/TestData/TestFont1 (UFO1).ufo/groups.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/groups.plist similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/groups.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/groups.plist diff --git a/TestData/TestFont1 (UFO1).ufo/kerning.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/kerning.plist similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/kerning.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/kerning.plist diff --git a/TestData/TestFont1 (UFO1).ufo/lib.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/lib.plist similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/lib.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/lib.plist diff --git a/TestData/TestFont1 (UFO1).ufo/metainfo.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/metainfo.plist similarity index 100% rename from TestData/TestFont1 (UFO1).ufo/metainfo.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO1).ufo/metainfo.plist diff --git a/TestData/TestFont1 (UFO2).ufo/features.fea b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/features.fea similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/features.fea rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/features.fea diff --git a/TestData/TestFont1 (UFO2).ufo/fontinfo.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/fontinfo.plist similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/fontinfo.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/fontinfo.plist diff --git a/TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/glyphs/A_.glif similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/glyphs/A_.glif diff --git a/TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/glyphs/B_.glif similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/glyphs/B_.glif diff --git a/TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/glyphs/contents.plist similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/glyphs/contents.plist diff --git a/TestData/TestFont1 (UFO2).ufo/groups.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/groups.plist similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/groups.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/groups.plist diff --git a/TestData/TestFont1 (UFO2).ufo/kerning.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/kerning.plist similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/kerning.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/kerning.plist diff --git a/TestData/TestFont1 (UFO2).ufo/lib.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/lib.plist similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/lib.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/lib.plist diff --git a/TestData/TestFont1 (UFO2).ufo/metainfo.plist b/Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/metainfo.plist similarity index 100% rename from TestData/TestFont1 (UFO2).ufo/metainfo.plist rename to Lib/ufoLib/test/testdata/TestFont1 (UFO2).ufo/metainfo.plist diff --git a/TestData/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/bar/lol.txt b/Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/bar/lol.txt similarity index 100% rename from TestData/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/bar/lol.txt rename to Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/bar/lol.txt diff --git a/TestData/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/foo.txt b/Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/foo.txt similarity index 100% rename from TestData/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/foo.txt rename to Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.directory/foo.txt diff --git a/TestData/UFO3-Read Data.ufo/data/org.unifiedfontobject.file.txt b/Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.file.txt similarity index 100% rename from TestData/UFO3-Read Data.ufo/data/org.unifiedfontobject.file.txt rename to Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/data/org.unifiedfontobject.file.txt diff --git a/TestData/UFO3-Read Data.ufo/metainfo.plist b/Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/metainfo.plist similarity index 100% rename from TestData/UFO3-Read Data.ufo/metainfo.plist rename to Lib/ufoLib/test/testdata/UFO3-Read Data.ufo/metainfo.plist diff --git a/MANIFEST.in b/MANIFEST.in index 464aec90e..aff919126 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,6 @@ include README.md notes.txt LICENSE.txt include Documentation/Makefile recursive-include Documentation *.py *.rst -recursive-include Data *.plist *.glif -recursive-include TestData *.plist *.glif *.fea *.txt +recursive-include Lib/ufoLib/test/testdata *.plist *.glif *.fea *.txt include requirements.txt tox.ini diff --git a/setup.py b/setup.py index 540397c59..7e2f98912 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ setup_params = dict( long_description=long_description, package_dir={'': 'Lib'}, packages=find_packages('Lib'), + include_package_data=True, setup_requires=pytest_runner + wheel, tests_require=[ 'pytest>=3.0.2', diff --git a/tox.ini b/tox.ini index f24aae8d9..3edc2fe72 100644 --- a/tox.ini +++ b/tox.ini @@ -11,5 +11,6 @@ deps = pytest -rrequirements.txt commands = - # pass to pytest any extra positional arguments after `tox -- ...` - pytest {posargs} + # run the test suite against the package installed inside tox env. + # any extra positional arguments after `tox -- ...` are passed on to pytest + pytest {posargs:--pyargs ufoLib} From 116bc0b6d25007a4b40914ee191a487c6218370b Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 17 Oct 2016 19:38:28 +0100 Subject: [PATCH 23/64] setup.cfg: include LICENSE.txt in wheel distribution packages --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 290f61100..1c5b96e6d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,9 @@ formats = zip [aliases] test = pytest +[metadata] +license_file = LICENSE.txt + [tool:pytest] minversion = 3.0.2 testpaths = From c86c970219b697f88d6e87bfdbedbcc9db4046b0 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 18 Oct 2016 11:08:11 +0100 Subject: [PATCH 24/64] .gitignore: add .eggs/ and .tox/ folders .eggs/ is where setuptools downloads the setup and test requirements. .tox/ is the folder containing Tox's cache and virtual environments [skip ci] --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4da73de8d..7faed8c14 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ dist/ .DS_Store *.egg-info *.py[cod] +.eggs/ +.tox/ From 61b93e9a14ca6a8c45a3a4d084f4e9c4a1aa4a26 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 18 Oct 2016 11:39:06 +0100 Subject: [PATCH 25/64] configure deployment to Github Releases and PyPI upon tags --- .travis.yml | 67 +++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 274b9e543..4c9ed887f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,39 +8,34 @@ install: - pip install --upgrade pip setuptools wheel - pip install tox-travis script: tox -# after_success: -# - pip wheel --no-deps -w dist . -# - python setup.py sdist -# before_deploy: -# - export ZIP=$(ls dist/ufoLib*.zip) -# - export WHL=$(ls dist/ufoLib*.whl) -# -## Replace the encrypted Github api key and PyPI password to enable automatic -## deployment of wheel and sdist to Github Releases and PyPI on tags. -# -# deploy: -# # deploy to Github Releases on tags -# - provider: releases -# api_key: -# secure: H2rz0E/GjRrRmvf6EYWv7Fyu+y4qU+InGiYlQ6xoyrjmMFCTYiPNW0ysu8893JFILusfO48zzEix7HcumQXtJU16XYwqft9eiWIGzSlP1krusmpQ0yTnuy9wEQcd7fXLEQDfiVZpJxWpC1TtsuIdDqpSUFcHDg4hlvn1IIh+6HwRP6+M5Mprze087ydBCKkO4CvqR4olC7bkSPorRtqUsiUHlUxYFS+R75a8ghqVQbky7ew9/r90zljCBFwYTPEv9tQ7OxnAQiUaAYojceThsBrNP1lH83tOoqB2FkKr9I5dxi5gqNuUsJHh3QK0V59oQCxU+iVFYfrlIZHTBQ+2caxIeWm2k9RkMXXkbg1LyfpORt+5L+NcFNE4WT0DTlYrKnpW+jrak0LEWaI97+15uHahph3vOJgaZrTFvRQZUJTWCNcji2jGbZg+O3pS1Vtd1xEgJ1TcrkOYLYBMrVOywbuQL4sXofLxH3I5RwAenkgm5TwxtuiNxpUkqlEhzJ+GYwWtfb7/qtcLIK7Xn6cDdsQG6XYQMVt+HpCKgRLlrQC8si6l+jZBEtJ7o4IKeYbtybFCe5H3V0jnRFRyncPPdpBavqJ093Pr7fGLvmZuHl551WRtyPlBN4aMGsqvqUSjwr/TxsX5zZ1tQ4QTN2YTmCdeUO6P/fwTBf5p0F3QFo8= -# file: -# - "${ZIP}" -# - "${WHL}" -# skip_cleanup: true -# on: -# repo: unified-font-object/ufoLib -# tags: true -# all_branches: true -# python: 3.5 -# # deploy to PyPI on tags -# - provider: pypi -# server: https://upload.pypi.org/legacy/ -# on: -# repo: unified-font-object/ufoLib -# tags: true -# all_branches: true -# python: 3.5 -# user: anthrotype -# password: -# secure: B6rFOTA8ol1kt210q7USDVOzLFWUyMIwdZuaUdejYmUb0CXK6W8kKj0H01yBCgjkhtew9YEQSB62jFiGUOJH9L8JjpJO2RG8cHIIblXQfQ5DXogmcEtsAFotQz0o3UO/SYJaMY917XsPCqw3MTgksd2NWwO7H3YCjaWntN4zZxyTWowGr2Vhbj9s8e+MGMtrZ3Is91AcoXNI7HPj2oVhAtPP5GGpceIP7JlOGdHGUFJKuwJLRKVjEURWXN6ZOQNoxuG6KdkpGtJYCFgRCW65e48WpQs8lts/ByyOMyRItPIPI+K/cPOaIvZ7IUhu9SueBgKcW2YVzQrPzD07N5Lhb4LaKhqgMwpy7oWntnpO0/Xes7bonAoDYKPz7O3O3zl57fy1sVhGXZZNoGUfwyFz1PPecONavRpDQYCMEOq8BcENT86mf0xbAvtmTvBgos8NknmZ35ckUkyrye8t058CwW0yhEO2Pr36XPKoEP9EAiq2YRvINPYnI1T3jTPUUwnc3IAv4zQvPUKWqs2/hEVy8FTCz/0hBgDv2cs4pRcofTzfXSczGMfyuNS3i4n3CHc4FEOYP9MuEWGDC7KF4MVNsBWbh5pkISwhIwNbW1UDept2k+Dmb9XrSoCt5aSvEPJVT3YTA4qTGSUQvhzj1SvXi3s2lh+1WbibEJqNfpdobGg= -# distributions: sdist bdist_wheel +before_deploy: + - pip wheel --no-deps -w dist . + - python setup.py sdist + - export WHL=$(ls dist/ufoLib*.whl) + - export ZIP=$(ls dist/ufoLib*.zip) +deploy: + # deploy to Github Releases on tags + - provider: releases + api_key: + secure: ZwNCLnAvI2ftcH35Hk+jJ4cNWBRKqgjzyO3hdQT8Kbh2RyhKkpe7Dv+e3/ac2iaCkYPoGXGmrMVjNm6ny1HM5xuyXbIQuh34nmY1de4mdU4aTyWzGZ+E1SoajjDZVi5OhkqIm/FD/o5czIY8tv7YzwhVME/d5PHrBJ9vq91wu++Vx9hy/pocuS1YdBa53iFXDtF66zA8Jyw/qdallHEmN7ZMwasozW2X20Ry5rhFzgmx9oQ7R2v3jIUU0AMVJIY60Q2UwhI0XJIeTXQY4pxKgNU+0k4UQCRCCNbQgqcRvoVy8o5m9ofWtkVMmQM1c5UB+wD8IGJccVM608+/pbB3fHLk5TZOHKWRP1WyAWtQA29yDktPFdjLCYnfCt3oj10cTIs+Iphu7F9vAWt8g7fuyBYlqaqdjC2J2WcOesJzLAkalAPa/vat2T30xKNUmx6eV1Nu3X2BtuBy1gn6IcDZ+szySpls6ZM1oaOMmu2cRbwAQtszeKqIsJyx6atogeIeWTKsnC9QV1A/TR9Ku9L6YPF6bxmreW7DbKX0hoNmJL+VUiW8DealIgP/4tloI7VqRvbfL8AZp78RfWGK7ZzCK+QLI7Xt8cSu33HvRBWgWlsjIdA7Qw+vb8UtqVQPKOpPw+jraybSUBQ3bSjb4Zhi8sOpDhYnw1EXIZ4RdRqqVvE= + file: + - "${ZIP}" + - "${WHL}" + skip_cleanup: true + on: + repo: unified-font-object/ufoLib + tags: true + all_branches: true + python: 3.5 + # deploy to PyPI on tags + - provider: pypi + server: https://upload.pypi.org/legacy/ + on: + repo: unified-font-object/ufoLib + tags: true + all_branches: true + python: 3.5 + user: anthrotype + password: + secure: m0tl6kKKOE/V1WsTkdn1yCYdI6dEnZZbz0SChVm9XNuloTti++I25oJvfcDFDtq5K+gHZ8hPmym/sFH+dozjf39fCrURiFnL9tIumCJvehubDXYGw2ZLN8SLayG6QsdBqyjQB8mS9L+Ag0vaC10Nj6zgmXPQQERckqDiLfmT1A1IPDodu18hHH7FM4eNhhB1Ksaem9rurgpNHtORHJxab1aGVIe0kz6UR/e+ldtenyxWcHVX+04kTJkR7mz9c2lTq0DrVZ7uc9slURi5Mw4VgGHG6J/sCsqUFoqtESciN0+2OblacVbvu7avBsnFbSVOFdqurRNpHf1gCPt5cx0nIORpps9VE2TZEj0J4wmTamhUGelIuyqu6jGYExc94Ca6OYLQxqr+YhiPSl0uiWzVA0D7dmKx9EIl/RKtB3nAg9+BX7il2Xt/fPU0qJtMbYwSTtS/KDuNFys6z4wFcvAykdBbilX2ZCjbSP36uVI6HmwWJX+/kAFiGiuW0qQQ3Q64LgBu2t8Ho0HE+J5TmpE59eaOqir1yMd9wo89t0QJNO2zf1/0FO0lakol4+VX+btyt8QfCnBGGk7ZjK46h9oOMhZ5kjdVRqDwrsgy6InLbirQvDbhGI2NZInY5/Y8w8lCdExyfsELSOBwm86ovzIKAG4bPU3cEIjAHuJGBXBjqKk= + distributions: sdist bdist_wheel From 03114c3b47a1757e8c9d9a2659b5d45fd42ea73a Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 18 Oct 2016 11:40:10 +0100 Subject: [PATCH 26/64] setup.py: version 2.0.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7e2f98912..8c36761f5 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ wheel = ['wheel'] if needs_wheel else [] setup_params = dict( name="ufoLib", - version="2.0.dev1", + version="2.0.0", description="A low-level UFO reader and writer.", author="Just van Rossum, Tal Leming, Erik van Blokland, others", author_email="info@robofab.com", From 51da9ba81ea5fbc942556573bfc876fb66618493 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 18 Oct 2016 12:21:51 +0100 Subject: [PATCH 27/64] start new release cycle --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8c36761f5..5a268793d 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ wheel = ['wheel'] if needs_wheel else [] setup_params = dict( name="ufoLib", - version="2.0.0", + version="2.1.0.dev0", description="A low-level UFO reader and writer.", author="Just van Rossum, Tal Leming, Erik van Blokland, others", author_email="info@robofab.com", From 5526dc8e176923b15ea5a7d5052f52a046f61d25 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 18 Oct 2016 12:23:52 +0100 Subject: [PATCH 28/64] README.md: add PyPI badge [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 78ecccb8b..9a9dc4a6f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Build Status](https://api.travis-ci.org/unified-font-object/ufoLib.svg)](https://travis-ci.org/unified-font-object/ufoLib) [![AppVeyor Status](https://ci.appveyor.com/api/projects/status/github/unified-font-object/ufoLib?svg=true)](https://ci.appveyor.com/project/adrientetar/ufolib) ![Python Versions](https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5-blue.svg) +[![PyPI](https://img.shields.io/pypi/v/ufoLib.svg)](https://pypi.org/project/ufoLib/) ufoLib ------ From 50f72da594cc6591b02754ac823449c10c8edb1b Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 21 Dec 2016 16:44:04 +0000 Subject: [PATCH 29/64] Update fonttools from 3.1.2 to 3.4.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2a2e55f66..d90c4b4a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.1.2 +fonttools==3.4.0 From c2f929ea7a6c1b68c1c531e69fe95cc1cdfd2ae0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 14 Jan 2017 15:50:49 +0000 Subject: [PATCH 30/64] Update fonttools from 3.4.0 to 3.5.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d90c4b4a2..ddb7dfb8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.4.0 +fonttools==3.5.0 From ae986dae8552ef334fe3fbafe6f0a1118a5d77a4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 6 Feb 2017 14:43:38 +0100 Subject: [PATCH 31/64] Update fonttools from 3.5.0 to 3.6.3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ddb7dfb8d..abc5adfd0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.5.0 +fonttools==3.6.3 From fde6540b01a6c0682168b3e6f83de7c045e46233 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 6 Feb 2017 19:04:57 +0000 Subject: [PATCH 32/64] add .pyup.yml for pyup.io bot; updates are now scheduled weekly --- .pyup.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .pyup.yml diff --git a/.pyup.yml b/.pyup.yml new file mode 100644 index 000000000..ed0ac860b --- /dev/null +++ b/.pyup.yml @@ -0,0 +1,5 @@ +# controls the frequency of updates (undocumented beta feature) +schedule: every week + +# do not pin dependencies unless they have explicit version specifiers +pin: False From fd8a1a6fdbcce54dd310bec95c4787d76c2bace8 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 13 Feb 2017 05:13:05 -0800 Subject: [PATCH 33/64] Update fonttools from 3.6.3 to 3.7.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index abc5adfd0..5f9dc3dcb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.6.3 +fonttools==3.7.0 From c8415152e8e4e11a84d773e50f80c06ce61858e4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 20 Feb 2017 05:13:05 -0800 Subject: [PATCH 34/64] Update fonttools from 3.7.0 to 3.7.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5f9dc3dcb..7e68f46a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.7.0 +fonttools==3.7.2 From 6739bc8b9db8ba0afe5b8f39ab6a4bf79bd72580 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 27 Feb 2017 12:45:16 +0000 Subject: [PATCH 35/64] [pointPen] add identifier=None argument in AbstractPointPen.beginPath, addPoint and addComponent methods --- Lib/ufoLib/pointPen.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/ufoLib/pointPen.py b/Lib/ufoLib/pointPen.py index 8bc6fe96d..abe973de8 100644 --- a/Lib/ufoLib/pointPen.py +++ b/Lib/ufoLib/pointPen.py @@ -23,7 +23,7 @@ class AbstractPointPen(object): Baseclass for all PointPens. """ - def beginPath(self): + def beginPath(self, identifier=None): """Start a new sub path.""" raise NotImplementedError @@ -31,11 +31,12 @@ class AbstractPointPen(object): """End the current sub path.""" raise NotImplementedError - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): + def addPoint(self, pt, segmentType=None, smooth=False, name=None, + identifier=None, **kwargs): """Add a point to the current sub path.""" raise NotImplementedError - def addComponent(self, baseGlyphName, transformation): + def addComponent(self, baseGlyphName, transformation, identifier=None): """Add a sub glyph.""" raise NotImplementedError From 869a8f6a450f5fabe6d6b4d9b15a7552985dc373 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 27 Feb 2017 16:04:47 +0000 Subject: [PATCH 36/64] [pointPen] add **kwargs to AbstractPointPen's beginPath and addCompoment too --- Lib/ufoLib/pointPen.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/ufoLib/pointPen.py b/Lib/ufoLib/pointPen.py index abe973de8..c1397b698 100644 --- a/Lib/ufoLib/pointPen.py +++ b/Lib/ufoLib/pointPen.py @@ -23,7 +23,7 @@ class AbstractPointPen(object): Baseclass for all PointPens. """ - def beginPath(self, identifier=None): + def beginPath(self, identifier=None, **kwargs): """Start a new sub path.""" raise NotImplementedError @@ -36,7 +36,8 @@ class AbstractPointPen(object): """Add a point to the current sub path.""" raise NotImplementedError - def addComponent(self, baseGlyphName, transformation, identifier=None): + def addComponent(self, baseGlyphName, transformation, identifier=None, + **kwargs): """Add a sub glyph.""" raise NotImplementedError From 0bc4ab949772b28cb890cba193d342adcf5905f6 Mon Sep 17 00:00:00 2001 From: Ben Kiel Date: Mon, 27 Feb 2017 10:18:02 -0600 Subject: [PATCH 37/64] Remove duplicate variable assignment --- Lib/ufoLib/glifLib.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index af1ee688e..d0ac51089 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -1019,11 +1019,6 @@ pointTypeOptions = set(["move", "line", "offcurve", "curve", "qcurve"]) # format 1 -componentAttributesFormat1 = set(["base", "xScale", "xyScale", "yxScale", "yScale", "xOffset", "yOffset"]) -pointAttributesFormat1 = set(["x", "y", "type", "smooth", "name"]) -pointSmoothOptions = set(("no", "yes")) -pointTypeOptions = set(["move", "line", "offcurve", "curve", "qcurve"]) - def buildOutlineFormat1(glyphObject, pen, outline): anchors = [] for element in outline: From 99c5f8491d330bcb0e38f74b656f9a72756e4f95 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 6 Mar 2017 13:17:05 +0000 Subject: [PATCH 38/64] Update fonttools from 3.7.2 to 3.8.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7e68f46a3..f20457dbf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.7.2 +fonttools==3.8.0 From 6e6a7e4afb0619b11d8713463fa9b6a423925efe Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 27 Mar 2017 14:28:06 +0100 Subject: [PATCH 39/64] Update fonttools from 3.8.0 to 3.9.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f20457dbf..1e6af4e15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.8.0 +fonttools==3.9.1 From cd605b44447e7551091e708efa26ca7fc4176910 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 24 Apr 2017 14:41:05 +0100 Subject: [PATCH 40/64] Update fonttools from 3.9.1 to 3.10.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1e6af4e15..91a774c51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.9.1 +fonttools==3.10.0 From d952f9a74fa40c667e0b1d5f6423c398b32ef259 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 25 Apr 2017 10:23:38 +0100 Subject: [PATCH 41/64] [pointPen] make PointToSegmentPen.addComponent accept/ignore **kwargs The beginPath and addPoint methods of PointToSegmentPen already take extra **kwargs (via BasePointToSegmentPen); only the addComponent method does not. This makes it raise TypeError when an unknown keyword argument is passed (e.g. 'identifier' for UFO3 objects). For the PointToSegmentPen, which translates between the PointPen and the SegmentPen API, it is ok if we only use the arguments that have an equivalent in the SegmentPen API, and silently discard the **kwargs. --- Lib/ufoLib/pointPen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ufoLib/pointPen.py b/Lib/ufoLib/pointPen.py index c1397b698..7bedf52c1 100644 --- a/Lib/ufoLib/pointPen.py +++ b/Lib/ufoLib/pointPen.py @@ -192,7 +192,7 @@ class PointToSegmentPen(BasePointToSegmentPen): else: pen.endPath() - def addComponent(self, glyphName, transform): + def addComponent(self, glyphName, transform, **kwargs): self.pen.addComponent(glyphName, transform) From e883885c3ab1dda95995092982108df4ff5a6809 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 25 Apr 2017 10:50:21 +0100 Subject: [PATCH 42/64] [pointPen] add ReverseContourPointPen from defcon https://github.com/typesupply/defcon/issues/106 --- Lib/ufoLib/pointPen.py | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/Lib/ufoLib/pointPen.py b/Lib/ufoLib/pointPen.py index 7bedf52c1..805949175 100644 --- a/Lib/ufoLib/pointPen.py +++ b/Lib/ufoLib/pointPen.py @@ -322,3 +322,85 @@ class GuessSmoothPointPen(AbstractPointPen): def addComponent(self, glyphName, transformation): assert self._points is None self._outPen.addComponent(glyphName, transformation) + + +class ReverseContourPointPen(AbstractPointPen): + """ + This is a PointPen that passes outline data to another PointPen, but + reversing the winding direction of all contours. Components are simply + passed through unchanged. + + Closed contours are reversed in such a way that the first point remains + the first point. + """ + + def __init__(self, outputPointPen): + self.pen = outputPointPen + # a place to store the points for the current sub path + self.currentContour = None + + def _flushContour(self): + pen = self.pen + contour = self.currentContour + if not contour: + pen.beginPath(identifier=self.currentContourIdentifier) + pen.endPath() + return + + closed = contour[0][1] != "move" + if not closed: + lastSegmentType = "move" + else: + # Remove the first point and insert it at the end. When + # the list of points gets reversed, this point will then + # again be at the start. In other words, the following + # will hold: + # for N in range(len(originalContour)): + # originalContour[N] == reversedContour[-N] + contour.append(contour.pop(0)) + # Find the first on-curve point. + firstOnCurve = None + for i in range(len(contour)): + if contour[i][1] is not None: + firstOnCurve = i + break + if firstOnCurve is None: + # There are no on-curve points, be basically have to + # do nothing but contour.reverse(). + lastSegmentType = None + else: + lastSegmentType = contour[firstOnCurve][1] + + contour.reverse() + if not closed: + # Open paths must start with a move, so we simply dump + # all off-curve points leading up to the first on-curve. + while contour[0][1] is None: + contour.pop(0) + pen.beginPath(identifier=self.currentContourIdentifier) + for pt, nextSegmentType, smooth, name, kwargs in contour: + if nextSegmentType is not None: + segmentType = lastSegmentType + lastSegmentType = nextSegmentType + else: + segmentType = None + pen.addPoint(pt, segmentType=segmentType, smooth=smooth, name=name, **kwargs) + pen.endPath() + + def beginPath(self, identifier=None, **kwargs): + assert self.currentContour is None + self.currentContour = [] + self.currentContourIdentifier = identifier + self.onCurve = [] + + def endPath(self): + assert self.currentContour is not None + self._flushContour() + self.currentContour = None + + def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): + self.currentContour.append((pt, segmentType, smooth, name, kwargs)) + + def addComponent(self, glyphName, transform, identifier=None, **kwargs): + assert self.currentContour is None + self.pen.addComponent(glyphName, transform, identifier=identifier, **kwargs) From ddf5000d45512cc2f9347747c6a2d434877ead33 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 22 May 2017 15:44:05 +0200 Subject: [PATCH 43/64] Update fonttools from 3.10.0 to 3.12.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 91a774c51..dad6c343e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.10.0 +fonttools==3.12.1 From 16abd8130c6e2d86586afac7b96c1d78520630dc Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Wed, 24 May 2017 18:37:52 +0100 Subject: [PATCH 44/64] Add bump_version and release commands to setup.py --- Lib/ufoLib/__init__.py | 2 + setup.cfg | 26 ++++++++ setup.py | 148 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 169 insertions(+), 7 deletions(-) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index 9b2ec400d..668452672 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -56,6 +56,8 @@ __all__ = [ "convertUFOFormatVersion1ToFormatVersion2" ] +__version__ = "2.1.0.dev0" + class UFOLibError(Exception): pass diff --git a/setup.cfg b/setup.cfg index 1c5b96e6d..7f4ed1603 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,29 @@ +[bumpversion] +current_version = 2.1.0.dev0 +commit = True +tag = False +tag_name = v{new_version} +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? +serialize = + {major}.{minor}.{patch}.{release}{dev} + {major}.{minor}.{patch} + +[bumpversion:part:release] +optional_value = final +values = + dev + final + +[bumpversion:part:dev] + +[bumpversion:file:Lib/ufoLib/__init__.py] +search = __version__ = "{current_version}" +replace = __version__ = "{new_version}" + +[bumpversion:file:setup.py] +search = version="{current_version}" +replace = version="{new_version}" + [wheel] universal = 1 diff --git a/setup.py b/setup.py index 5a268793d..db3bb2610 100755 --- a/setup.py +++ b/setup.py @@ -1,8 +1,143 @@ #! /usr/bin/env python import sys -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Command +from distutils import log +class bump_version(Command): + + description = "increment the package version and commit the changes" + + user_options = [ + ("major", None, "bump the first digit, for incompatible API changes"), + ("minor", None, "bump the second digit, for new backward-compatible features"), + ("patch", None, "bump the third digit, for bug fixes (default)"), + ] + + def initialize_options(self): + self.minor = False + self.major = False + self.patch = False + + def finalize_options(self): + part = None + for attr in ("major", "minor", "patch"): + if getattr(self, attr, False): + if part is None: + part = attr + else: + from distutils.errors import DistutilsOptionError + raise DistutilsOptionError( + "version part options are mutually exclusive") + self.part = part or "patch" + + def bumpversion(self, part, **kwargs): + """ Run bumpversion.main() with the specified arguments. + """ + import bumpversion + + args = ['--verbose'] if self.verbose > 1 else [] + for k, v in kwargs.items(): + k = "--{}".format(k.replace("_", "-")) + is_bool = isinstance(v, bool) and v is True + args.extend([k] if is_bool else [k, str(v)]) + args.append(part) + + log.debug( + "$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args)) + + bumpversion.main(args) + + def run(self): + log.info("bumping '%s' version" % self.part) + self.bumpversion(self.part) + + +class release(bump_version): + """Drop the developmental release '.devN' suffix from the package version, + open the default text $EDITOR to write release notes, commit the changes + and generate a git tag. + + Release notes can also be set with the -m/--message option, or by reading + from standard input. + """ + + description = "tag a new release" + + user_options = [ + ("message=", 'm', "message containing the release notes"), + ] + + def initialize_options(self): + self.message = None + + def finalize_options(self): + import re + + current_version = self.distribution.metadata.get_version() + if not re.search(r"\.dev[0-9]+", current_version): + from distutils.errors import DistutilsSetupError + raise DistutilsSetupError( + "current version (%s) has no '.devN' suffix.\n " + "Run 'setup.py bump_version' with any of " + "--major, --minor, --patch options" % current_version) + + message = self.message + if message is None: + if sys.stdin.isatty(): + # stdin is interactive, use editor to write release notes + message = self.edit_release_notes() + else: + # read release notes from stdin pipe + message = sys.stdin.read() + + if not message.strip(): + from distutils.errors import DistutilsSetupError + raise DistutilsSetupError("release notes message is empty") + + self.message = "v{new_version}\n\n%s" % message + + @staticmethod + def edit_release_notes(): + """Use the default text $EDITOR to write release notes. + If $EDITOR is not set, use 'nano'.""" + from tempfile import mkstemp + import os + import shlex + import subprocess + + text_editor = shlex.split(os.environ.get('EDITOR', 'nano')) + + fd, tmp = mkstemp(prefix='bumpversion-') + try: + os.close(fd) + with open(tmp, 'w') as f: + f.write("\n\n# Write release notes.\n" + "# Lines starting with '#' will be ignored.") + subprocess.check_call(text_editor + [tmp]) + with open(tmp, 'r') as f: + changes = "".join( + l for l in f.readlines() if not l.startswith('#')) + finally: + os.remove(tmp) + return changes + + def run(self): + log.info("stripping developmental release suffix") + # drop '.dev0' suffix, commit with given message and create git tag + self.bumpversion("release", + tag=True, + message="Release {new_version}", + tag_message=self.message) + + +needs_pytest = {'pytest', 'test'}.intersection(sys.argv) +pytest_runner = ['pytest_runner'] if needs_pytest else [] +needs_wheel = {'bdist_wheel'}.intersection(sys.argv) +wheel = ['wheel'] if needs_wheel else [] +needs_bump2version = {'release', 'bump_version'}.intersection(sys.argv) +bump2version = ['bump2version'] if needs_bump2version else [] + long_description = """\ ufoLib reads and writes Unified Font Object (UFO) files. UFO is a file format that stores fonts source files. @@ -10,11 +145,6 @@ UFO is a file format that stores fonts source files. http://unifiedfontobject.org """ -needs_pytest = {'pytest', 'test'}.intersection(sys.argv) -pytest_runner = ['pytest_runner'] if needs_pytest else [] -needs_wheel = {'bdist_wheel'}.intersection(sys.argv) -wheel = ['wheel'] if needs_wheel else [] - setup_params = dict( name="ufoLib", version="2.1.0.dev0", @@ -30,13 +160,17 @@ setup_params = dict( package_dir={'': 'Lib'}, packages=find_packages('Lib'), include_package_data=True, - setup_requires=pytest_runner + wheel, + setup_requires=pytest_runner + wheel + bump2version, tests_require=[ 'pytest>=3.0.2', ], install_requires=[ "fonttools>=3.1.2", ], + cmdclass={ + "release": release, + "bump_version": bump_version, + }, classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", From 1c5f70c873f714527f09982a85defc7b0092d771 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Wed, 24 May 2017 18:38:10 +0100 Subject: [PATCH 45/64] Release 2.1.0 --- Lib/ufoLib/__init__.py | 2 +- setup.cfg | 21 +++++++++++---------- setup.py | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index 668452672..1b666281c 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -56,7 +56,7 @@ __all__ = [ "convertUFOFormatVersion1ToFormatVersion2" ] -__version__ = "2.1.0.dev0" +__version__ = "2.1.0" class UFOLibError(Exception): pass diff --git a/setup.cfg b/setup.cfg index 7f4ed1603..09535f88c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.1.0.dev0 +current_version = 2.1.0 commit = True tag = False tag_name = v{new_version} @@ -38,12 +38,13 @@ license_file = LICENSE.txt [tool:pytest] minversion = 3.0.2 -testpaths = - Lib/ufoLib -addopts = - # run py.test in verbose mode - -v - # show extra test summary info - -r a - # run doctests in all .py modules - --doctest-modules +testpaths = + Lib/ufoLib +addopts = + # run py.test in verbose mode + -v + # show extra test summary info + -r a + # run doctests in all .py modules + --doctest-modules + diff --git a/setup.py b/setup.py index db3bb2610..09c1e7ed0 100755 --- a/setup.py +++ b/setup.py @@ -147,7 +147,7 @@ http://unifiedfontobject.org setup_params = dict( name="ufoLib", - version="2.1.0.dev0", + version="2.1.0", description="A low-level UFO reader and writer.", author="Just van Rossum, Tal Leming, Erik van Blokland, others", author_email="info@robofab.com", From ab9985ee3aaff580e29c57e954d49a35858eb81b Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Wed, 24 May 2017 18:39:50 +0100 Subject: [PATCH 46/64] =?UTF-8?q?Bump=20version:=202.1.0=20=E2=86=92=202.1?= =?UTF-8?q?.1.dev0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/ufoLib/__init__.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index 1b666281c..02e2b6ccd 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -56,7 +56,7 @@ __all__ = [ "convertUFOFormatVersion1ToFormatVersion2" ] -__version__ = "2.1.0" +__version__ = "2.1.1.dev0" class UFOLibError(Exception): pass diff --git a/setup.cfg b/setup.cfg index 09535f88c..2a8e34e20 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.1.0 +current_version = 2.1.1.dev0 commit = True tag = False tag_name = v{new_version} diff --git a/setup.py b/setup.py index 09c1e7ed0..2367fbbf0 100755 --- a/setup.py +++ b/setup.py @@ -147,7 +147,7 @@ http://unifiedfontobject.org setup_params = dict( name="ufoLib", - version="2.1.0", + version="2.1.1.dev0", description="A low-level UFO reader and writer.", author="Just van Rossum, Tal Leming, Erik van Blokland, others", author_email="info@robofab.com", From bf27243b0dd6be69dd38572f224832f07c0089c1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 5 Jun 2017 14:57:05 +0100 Subject: [PATCH 47/64] Update fonttools from 3.12.1 to 3.13.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dad6c343e..1f61c3241 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -fonttools==3.12.1 +fonttools==3.13.1 From 50f381dec6184a3e040f23d58f9c7421b879cca5 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 10:14:16 +0100 Subject: [PATCH 48/64] test_UFOConversion: remove test1To2 as support for UFO1 is deprecated --- Lib/ufoLib/test/test_UFOConversion.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Lib/ufoLib/test/test_UFOConversion.py b/Lib/ufoLib/test/test_UFOConversion.py index cd7a36afe..f0efa4c5b 100644 --- a/Lib/ufoLib/test/test_UFOConversion.py +++ b/Lib/ufoLib/test/test_UFOConversion.py @@ -4,7 +4,7 @@ import shutil import unittest import tempfile from io import open -from ufoLib import convertUFOFormatVersion1ToFormatVersion2, UFOReader, UFOWriter +from ufoLib import UFOReader, UFOWriter from ufoLib.plistlib import readPlist, writePlist from ufoLib.test.testSupport import expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion @@ -116,13 +116,6 @@ class ConversionFunctionsTestCase(unittest.TestCase): data2 = readPlist(f) self.assertEqual(data1, data2) - def test1To2(self): - path1 = self.getFontPath("TestFont1 (UFO1).ufo") - path2 = self.getFontPath("TestFont1 (UFO1) converted.ufo") - path3 = self.getFontPath("TestFont1 (UFO2).ufo") - convertUFOFormatVersion1ToFormatVersion2(path1, path2) - self.compareFileStructures(path2, path3, expectedFontInfo1To2Conversion, False) - # --------------------- # kerning up conversion From af8b0e322457dafededbae08d022fef757299283 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 10:14:58 +0100 Subject: [PATCH 49/64] [filesystem] use removetree as removedir was removed from pyfilesystem2 https://github.com/PyFilesystem/pyfilesystem2/issues/32 --- Lib/ufoLib/filesystem.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index ab92dfecc..5164c6542 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -119,9 +119,9 @@ class FileSystem(object): path = self._fsRootPath(path) self._fs.makedir(path) - def _fsRemoveDirectory(self, path): + def _fsRemoveTree(self, path): path = self._fsRootPath(path) - self._fs.removedir(path, force=True) + self._fs.removetree(path) def _fsMove(self, path1, path2): if self.isDirectory(path1): @@ -319,7 +319,7 @@ class FileSystem(object): raise UFOLibError("The file %s does not exist." % path) else: if self.isDirectory(path): - self._fsRemoveDirectory(path) + self._fsRemoveTree(path) else: self._fsRemove(path) directory = self.directoryName(path) @@ -330,7 +330,7 @@ class FileSystem(object): if not self.exists(directory): return if not len(self._fsListDirectory(directory)): - self._fsRemoveDirectory(directory) + self._fsRemoveTree(directory) else: return directory = self.directoryName(directory) @@ -436,7 +436,7 @@ class _NOFS(object): path = self._absPath(path) os.mkdir(path) - def removedir(self, path): + def removetree(self, path): path = self._absPath(path) shutil.rmtree(path) From 3c49e59864e35d79056c484507db9c438031f0b4 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 12:16:01 +0100 Subject: [PATCH 50/64] [ci] test with and without fs; test on 3.6 instead of 3.4 --- .travis.yml | 28 +++++++++++++++++------- appveyor.yml | 60 +++++++++++++++++++++++++++++++++++++++++----------- tox.ini | 5 +++-- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4c9ed887f..d2f926a14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,24 @@ language: python sudo: false -python: - - "2.7" - - "3.4" - - "3.5" +matrix: + include: + - python: 2.7 + env: TOXENV=py27-fs + - python: 2.7 + env: TOXENV=py27-nofs + - python: 3.5 + env: TOXENV=py35-fs + - python: 3.5 + env: TOXENV=py35-nofs + - python: 3.6 + env: TOXENV=py36-fs + - python: 3.6 + env: + - TOXENV=py36-nofs + - BUILD_DIST=true + install: - - pip install --upgrade pip setuptools wheel - - pip install tox-travis + - pip install --upgrade pip setuptools wheel tox script: tox before_deploy: - pip wheel --no-deps -w dist . @@ -26,7 +38,7 @@ deploy: repo: unified-font-object/ufoLib tags: true all_branches: true - python: 3.5 + condition: "$BUILD_DIST == true" # deploy to PyPI on tags - provider: pypi server: https://upload.pypi.org/legacy/ @@ -34,7 +46,7 @@ deploy: repo: unified-font-object/ufoLib tags: true all_branches: true - python: 3.5 + condition: "$BUILD_DIST == true" user: anthrotype password: secure: m0tl6kKKOE/V1WsTkdn1yCYdI6dEnZZbz0SChVm9XNuloTti++I25oJvfcDFDtq5K+gHZ8hPmym/sFH+dozjf39fCrURiFnL9tIumCJvehubDXYGw2ZLN8SLayG6QsdBqyjQB8mS9L+Ag0vaC10Nj6zgmXPQQERckqDiLfmT1A1IPDodu18hHH7FM4eNhhB1Ksaem9rurgpNHtORHJxab1aGVIe0kz6UR/e+ldtenyxWcHVX+04kTJkR7mz9c2lTq0DrVZ7uc9slURi5Mw4VgGHG6J/sCsqUFoqtESciN0+2OblacVbvu7avBsnFbSVOFdqurRNpHf1gCPt5cx0nIORpps9VE2TZEj0J4wmTamhUGelIuyqu6jGYExc94Ca6OYLQxqr+YhiPSl0uiWzVA0D7dmKx9EIl/RKtB3nAg9+BX7il2Xt/fPU0qJtMbYwSTtS/KDuNFys6z4wFcvAykdBbilX2ZCjbSP36uVI6HmwWJX+/kAFiGiuW0qQQ3Q64LgBu2t8Ho0HE+J5TmpE59eaOqir1yMd9wo89t0QJNO2zf1/0FO0lakol4+VX+btyt8QfCnBGGk7ZjK46h9oOMhZ5kjdVRqDwrsgy6InLbirQvDbhGI2NZInY5/Y8w8lCdExyfsELSOBwm86ovzIKAG4bPU3cEIjAHuJGBXBjqKk= diff --git a/appveyor.yml b/appveyor.yml index 7866cc5ee..a4078899d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,39 +4,75 @@ environment: - PYTHON: "C:\\Python27" PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "32" - TOXENV: "py27" + TOXENV: "py27-fs" TOXPYTHON: "C:\\Python27\\python.exe" - - PYTHON: "C:\\Python34" - PYTHON_VERSION: "3.4.x" + - PYTHON: "C:\\Python27" + PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "32" - TOXENV: "py34" - TOXPYTHON: "C:\\Python34\\python.exe" + TOXENV: "py27-nofs" + TOXPYTHON: "C:\\Python27\\python.exe" - PYTHON: "C:\\Python35" PYTHON_VERSION: "3.5.x" PYTHON_ARCH: "32" - TOXENV: "py35" + TOXENV: "py35-fs" TOXPYTHON: "C:\\Python35\\python.exe" + - PYTHON: "C:\\Python35" + PYTHON_VERSION: "3.5.x" + PYTHON_ARCH: "32" + TOXENV: "py35-nofs" + TOXPYTHON: "C:\\Python35\\python.exe" + + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "32" + TOXENV: "py36-fs" + TOXPYTHON: "C:\\Python36\\python.exe" + + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "32" + TOXENV: "py36-nofs" + TOXPYTHON: "C:\\Python36\\python.exe" + - PYTHON: "C:\\Python27-x64" PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "64" - TOXENV: "py27" + TOXENV: "py27-fs" TOXPYTHON: "C:\\Python27-x64\\python.exe" - - PYTHON: "C:\\Python34-x64" - PYTHON_VERSION: "3.4.x" + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "64" - TOXENV: "py34" - TOXPYTHON: "C:\\Python34-x64\\python.exe" + TOXENV: "py27-nofs" + TOXPYTHON: "C:\\Python27-x64\\python.exe" - PYTHON: "C:\\Python35-x64" PYTHON_VERSION: "3.5.x" PYTHON_ARCH: "64" - TOXENV: "py35" + TOXENV: "py35-fs" TOXPYTHON: "C:\\Python35-x64\\python.exe" + - PYTHON: "C:\\Python35-x64" + PYTHON_VERSION: "3.5.x" + PYTHON_ARCH: "64" + TOXENV: "py35-nofs" + TOXPYTHON: "C:\\Python35-x64\\python.exe" + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "64" + TOXENV: "py36-fs" + TOXPYTHON: "C:\\Python36-x64\\python.exe" + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "64" + TOXENV: "py36-nofs" + TOXPYTHON: "C:\\Python36-x64\\python.exe" + init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" diff --git a/tox.ini b/tox.ini index 3edc2fe72..11e9bbc50 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,16 @@ [tox] -envlist = py27, py35 +envlist = py{27,36}-{fs,nofs} [testenv] basepython = # we use TOXPYTHON env variable to specify the location of Appveyor Python py27: {env:TOXPYTHON:python2.7} - py34: {env:TOXPYTHON:python3.4} py35: {env:TOXPYTHON:python3.5} + py36: {env:TOXPYTHON:python3.6} deps = pytest -rrequirements.txt + fs: fs==2.0.4 commands = # run the test suite against the package installed inside tox env. # any extra positional arguments after `tox -- ...` are passed on to pytest From 758307d8bb8c90c1a2f0cad97d3bd26706bf449a Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 12:59:56 +0100 Subject: [PATCH 51/64] [filesystem] use os.path if not haveFS --- Lib/ufoLib/filesystem.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 5164c6542..128b15bdb 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -154,16 +154,28 @@ class FileSystem(object): # ----------------- def joinPath(self, *parts): - return fs.path.join(*parts) + if haveFS: + return fs.path.join(*parts) + else: + return os.path.join(*parts) def splitPath(self, path): - return fs.path.split(path) + if haveFS: + return fs.path.split(path) + else: + return os.path.split(path) def directoryName(self, path): - return fs.path.dirname(path) + if haveFS: + return fs.path.dirname(path) + else: + return os.path.dirname(path) def relativePath(self, path, start): - return fs.relativefrom(path, start) + if haveFS: + return fs.relativefrom(path, start) + else: + return os.path.relpath(path, start) # --------- # Existence @@ -409,18 +421,6 @@ class _NOFS(object): def _absPath(self, path): return os.path.join(self._path, path) - def joinPath(self, *parts): - return os.path.join(*parts) - - def splitPath(self, path): - return os.path.split(path) - - def directoryName(self, path): - return os.path.split(path)[0] - - def relativePath(self, path, start): - return os.path.relpath(path, start) - def close(self): pass From 07ba88f55a505469e692ecca7dacb8de1b16d790 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 13:13:15 +0100 Subject: [PATCH 52/64] [filesystem] fs.zipfs module no longer exports ZipOperError it's unused anyway --- Lib/ufoLib/filesystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 128b15bdb..6a4d68964 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -7,7 +7,7 @@ haveFS = False try: import fs from fs.osfs import OSFS - from fs.zipfs import ZipFS, ZipOpenError + from fs.zipfs import ZipFS haveFS = True except ImportError: pass From 62cc1c5805f4961454dc48fe85b3366ab8483ac3 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 13:23:04 +0100 Subject: [PATCH 53/64] [filesystem] allow to init FileSystem class from another instance --- Lib/ufoLib/filesystem.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 6a4d68964..fec6367e2 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -32,7 +32,7 @@ class FileSystem(object): def __init__(self, path, mode="r", structure=None): """ - path can be a path or a fs file system object. + path can be a path or another FileSystem object. mode can be r or w. @@ -41,11 +41,10 @@ class FileSystem(object): package: package structure zip: zipped package - mode and structure are both ignored if a - fs file system object is given for path. + mode and structure are both ignored if a FileSystem + object is given for path. """ self._root = None - self._path = "" if isinstance(path, basestring): self._path = path if mode == "w": @@ -58,7 +57,7 @@ class FileSystem(object): structure = existingStructure elif mode == "r": if not os.path.exists(path): - raise UFOLibError("The specified UFO doesn't exist.") + raise UFOLibError("The specified UFO doesn't exist: %r" % path) structure = sniffFileStructure(path) if structure == "package": if mode == "w" and not os.path.exists(path): @@ -75,6 +74,12 @@ class FileSystem(object): raise UFOLibError("The UFO contains more than one root.") else: self._root = roots[0] + elif isinstance(path, self.__class__): + self._root = path._root + self._path = path._path + path = path._fs + else: + raise TypeError(path) self._fs = path def close(self): From 590d00ba118953e84b9f3a1e127934eaccb52ee4 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 13:29:33 +0100 Subject: [PATCH 54/64] [filesystem] use unicode strings for path names as pyfilesystem2 works internally with unicode strings, some methods (e.g. self._fs.exists()) fail with TypeError if passed bytes strings (on python 2 bytes == str). We decode bytes path strings using the system's default filesystem encoding. --- Lib/ufoLib/filesystem.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index fec6367e2..2f9770ac8 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -1,7 +1,9 @@ import os +import sys import shutil from io import StringIO, BytesIO, open import zipfile +from fontTools.misc.py23 import tounicode haveFS = False try: @@ -20,6 +22,9 @@ try: except NameError: basestring = str +_SYS_FS_ENCODING = sys.getfilesystemencoding() + + def sniffFileStructure(path): if zipfile.is_zipfile(path): return "zip" @@ -46,6 +51,7 @@ class FileSystem(object): """ self._root = None if isinstance(path, basestring): + path = tounicode(path, encoding=_SYS_FS_ENCODING) self._path = path if mode == "w": if os.path.exists(path): @@ -99,6 +105,7 @@ class FileSystem(object): """ def _fsRootPath(self, path): + path = tounicode(path, encoding=_SYS_FS_ENCODING) if self._root is None: return path return self.joinPath(self._root, path) From 1de6eb1eb566ae1ac97b315f2e9c98d689806cc0 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 13:33:15 +0100 Subject: [PATCH 55/64] [filesystem] pass create=True to movedir otherwise it raises `ResourceNotFound` --- Lib/ufoLib/filesystem.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 2f9770ac8..5db4761ba 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -136,13 +136,12 @@ class FileSystem(object): self._fs.removetree(path) def _fsMove(self, path1, path2): - if self.isDirectory(path1): - meth = self._fs.movedir - else: - meth = self._fs.move path1 = self._fsRootPath(path1) path2 = self._fsRootPath(path2) - meth(path1, path2) + if self.isDirectory(path1): + self._fs.movedir(path1, path2, create=True) + else: + self._fs.move(path1, path2) def _fsExists(self, path): path = self._fsRootPath(path) From 337a68715e89bd2f01681826ec545470ccc8b2e5 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 13:41:14 +0100 Subject: [PATCH 56/64] [filesystem] getinfo now returns a namespace, not a dict We bumped the minimum required fonttools to 3.10, so we can use the backported SimpleNamespace as the return value of _NOFS.getinfo method. --- Lib/ufoLib/filesystem.py | 7 ++++--- setup.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 5db4761ba..d4237f922 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -158,7 +158,7 @@ class FileSystem(object): def _fsGetFileModificationTime(self, path): path = self._fsRootPath(path) info = self._fs.getinfo(path) - return info["modified_time"] + return info.modified # ----------------- # Path Manipulation @@ -474,10 +474,11 @@ class _NOFS(object): return os.listdir(path) def getinfo(self, path): + from fontTools.misc.py23 import SimpleNamespace path = self._absPath(path) stat = os.stat(path) - info = dict( - modified_time=stat.st_mtime + info = SimpleNamespace( + modified=stat.st_mtime ) return info diff --git a/setup.py b/setup.py index 2367fbbf0..9fc3005d3 100755 --- a/setup.py +++ b/setup.py @@ -165,7 +165,7 @@ setup_params = dict( 'pytest>=3.0.2', ], install_requires=[ - "fonttools>=3.1.2", + "fonttools>=3.10.0", ], cmdclass={ "release": release, From 46f32d4cd41ab5a26284a396728d50b15b64c473 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 13:43:33 +0100 Subject: [PATCH 57/64] [filesystem] print exception in readPlist --- Lib/ufoLib/filesystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index d4237f922..aa3eec345 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -395,8 +395,8 @@ class FileSystem(object): try: with self.open(path, "rb") as f: return readPlist(f) - except: - raise UFOLibError("The file %s could not be read." % path) + except Exception as e: + raise UFOLibError("The file %s could not be read: %s" % (path, str(e))) def writePlist(self, path, obj): """ From 3c0d41bac9d71c6e98daa631148c175b55adc959 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 13:50:15 +0100 Subject: [PATCH 58/64] [filesystem] use the current ZipFS API; create "contents" dir The 'mode' and 'allow_zip_64' arguments are no longer there in pyfileststem2's zipfs module; allow_zip_64 is the default now, and the 'mode' is replaced by a 'write' boolean argument The "contents" root dir must be created manually. --- Lib/ufoLib/filesystem.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index aa3eec345..37ad153c7 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -72,10 +72,12 @@ class FileSystem(object): elif structure == "zip": if not haveFS: raise UFOLibError("The fs module is required for reading and writing UFO ZIP.") - path = ZipFS(path, mode=mode, allow_zip_64=True, encoding="utf8") + path = ZipFS( + path, write=True if mode == 'w' else False, encoding="utf8") roots = path.listdir("") if not roots: - self._root = "contents" + self._root = u"contents" + path.makedir(self._root) elif len(roots) > 1: raise UFOLibError("The UFO contains more than one root.") else: From 22a6f917e43eb5deb9add3d94acdeb268212a22d Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 14:04:10 +0100 Subject: [PATCH 59/64] [filesystem] implement 'create' argument for _NOFS.movedir in pyfilesystem2, movedir copies the content from one directory to antoher, optionally creating the destination if create=True. When the destination is exists and is a directory, shutil.move moves the source directory inside the destinatinn directory. shutil.copytree also fails if the destination already exists; so here we resort to distutils.dir_util.copy_tree (it's part of python std lib) --- Lib/ufoLib/filesystem.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 37ad153c7..7d3af977e 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -458,10 +458,35 @@ class _NOFS(object): path2 = self._absPath(path2) os.move(path1, path2) - def movedir(self, path1, path2): + def movedir(self, path1, path2, create=False): path1 = self._absPath(path1) path2 = self._absPath(path2) - shutil.move(path1, path2) + exists = False + if not create: + if not os.path.exists(path2): + raise UFOLibError("%r not found" % path2) + elif not os.path.isdir(path2): + raise UFOLibError("%r should be a directory" % path2) + else: + exists = True + else: + if os.path.exists(path2): + if not os.path.isdir(path2): + raise UFOLibError("%r should be a directory" % path2) + else: + exists = True + if exists: + # if destination is an existing directory, shutil.move then moves + # the source directory inside that directory; in pyfilesytem2, + # movedir only moves the content between the src and dst folders. + # Here we use distutils' copy_tree instead of shutil.copytree, as + # the latter does not work if destination exists + from distutils.dir_util import copy_tree + copy_tree(path1, path2) + shutil.rmtree(path1) + else: + # shutil.move creates destination if not exists yet + shutil.move(path1, path2) def exists(self, path): path = self._absPath(path) From c06c38cadb9fa0e426eca6d46368ff11da5de0c8 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 14:05:59 +0100 Subject: [PATCH 60/64] [ufoLib] UFOReader.getFileModificationTime is used by defcon --- Lib/ufoLib/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index 18966750d..214fa0eb9 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -171,6 +171,11 @@ class UFOReader(object): self._upConvertedKerningData["groups"] = groups self._upConvertedKerningData["groupRenameMaps"] = conversionMaps + # support methods + + def getFileModificationTime(self, path): + return self.fileSystem.getFileModificationTime(path) + # metainfo.plist def readMetaInfo(self): From e3d89cee7f2c7434e2fdaecdf2ce091c2e4e0ad3 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 14:08:41 +0100 Subject: [PATCH 61/64] [ufoLib] fix listing images dir; pass a file object to pngValidator --- Lib/ufoLib/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index 214fa0eb9..29dabff49 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -434,13 +434,14 @@ class UFOReader(object): if not self.fileSystem.isDirectory(IMAGES_DIRNAME): raise UFOLibError("The UFO contains an \"images\" file instead of a directory.") result = [] - for fileName in self.fileSystem.listDirectory(path): - if self.fileSystem.isDirectory(fileName): + for fileName in self.fileSystem.listDirectory(IMAGES_DIRNAME): + path = self.fileSystem.joinPath(IMAGES_DIRNAME, fileName) + if self.fileSystem.isDirectory(path): # silently skip this as version control # systems often have hidden directories continue - # XXX this is sending a path to the validator. that won't work in the abstracted filesystem. - valid, error = pngValidator(path=p) + with self.fileSystem.open(path, mode='rb') as fp: + valid, error = pngValidator(fileObj=fp) if valid: result.append(fileName) return result From 2bef40c30d811d9a0e48fe219f866d2be99bc750 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 14:11:13 +0100 Subject: [PATCH 62/64] [ufoLib] use self.fileSystem.joinPath instead of reader.joinPath --- Lib/ufoLib/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ufoLib/__init__.py b/Lib/ufoLib/__init__.py index 29dabff49..f37ebd4e6 100755 --- a/Lib/ufoLib/__init__.py +++ b/Lib/ufoLib/__init__.py @@ -1028,8 +1028,8 @@ class UFOWriter(object): """ if self._formatVersion < 3: raise UFOLibError("Images are not allowed in UFO %d." % self._formatVersion) - sourcePath = reader.joinPath(IMAGES_DIRNAME, sourceFileName) - destPath = self.joinPath(IMAGES_DIRNAME, destFileName) + sourcePath = self.fileSystem.joinPath(IMAGES_DIRNAME, sourceFileName) + destPath = self.fileSystem.joinPath(IMAGES_DIRNAME, destFileName) self.copyFromReader(reader, sourcePath, destPath) From ec8d58f0b83a2db5b88fbf7179eaf92371b3a333 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 16:40:28 +0100 Subject: [PATCH 63/64] [filesystem] in listDirectory, always use Unix '/' as separator the _NOFS implementation is using `os.listdir` which uses the native `os.sep`; however, pyfilesystem2 FS paths always uses forward slashes '/'. --- Lib/ufoLib/filesystem.py | 8 ++++++-- Lib/ufoLib/test/test_UFO3.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 7d3af977e..4fa2e530b 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -204,8 +204,9 @@ class FileSystem(object): return self._listDirectory(path, recurse=recurse, relativeTo=path) def _listDirectory(self, path, recurse=False, relativeTo=None, depth=0, maxDepth=100): - if not relativeTo.endswith("/"): - relativeTo += "/" + sep = os.sep + if not relativeTo.endswith(sep): + relativeTo += sep if depth > maxDepth: raise UFOLibError("Maximum recusion depth reached.") result = [] @@ -215,6 +216,9 @@ class FileSystem(object): result += self._listDirectory(p, recurse=True, relativeTo=relativeTo, depth=depth+1, maxDepth=maxDepth) else: p = p[len(relativeTo):] + if sep != "/": + # replace '\\' with '/' + path = path.replace(sep, "/") result.append(p) return result diff --git a/Lib/ufoLib/test/test_UFO3.py b/Lib/ufoLib/test/test_UFO3.py index 4410cd52d..87f82b258 100644 --- a/Lib/ufoLib/test/test_UFO3.py +++ b/Lib/ufoLib/test/test_UFO3.py @@ -4147,8 +4147,8 @@ class UFO3ReadDataTestCase(unittest.TestCase): reader = UFOReader(self.getFontPath()) found = reader.getDataDirectoryListing() expected = [ - 'org.unifiedfontobject.directory%(s)sbar%(s)slol.txt' % {'s': os.sep}, - 'org.unifiedfontobject.directory%(s)sfoo.txt' % {'s': os.sep}, + 'org.unifiedfontobject.directory/bar/lol.txt', + 'org.unifiedfontobject.directory/foo.txt', 'org.unifiedfontobject.file.txt' ] self.assertEqual(set(found), set(expected)) From a06da7681e7e38971519315bb74abb5abb4d862c Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 21 Jul 2017 16:45:22 +0100 Subject: [PATCH 64/64] fixup --- Lib/ufoLib/filesystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ufoLib/filesystem.py b/Lib/ufoLib/filesystem.py index 4fa2e530b..ab2b72340 100644 --- a/Lib/ufoLib/filesystem.py +++ b/Lib/ufoLib/filesystem.py @@ -218,7 +218,7 @@ class FileSystem(object): p = p[len(relativeTo):] if sep != "/": # replace '\\' with '/' - path = path.replace(sep, "/") + p = p.replace(sep, "/") result.append(p) return result