Handle inclusion of OpenType feature files

This commit is contained in:
Sascha Brawer 2015-07-31 17:09:19 +02:00
parent ac700b0af5
commit efbcba79a4
11 changed files with 108 additions and 2 deletions

View File

@ -1,5 +1,7 @@
from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
import codecs
import os
class LexerError(Exception):
@ -8,7 +10,7 @@ class LexerError(Exception):
self.location = location
class Lexer:
class Lexer(object):
NUMBER = "NUMBER"
STRING = "STRING"
NAME = "NAME"
@ -129,3 +131,48 @@ class Lexer:
while p < self.text_length_ and self.text_[p] not in stop_at:
p += 1
self.pos_ = p
class IncludingLexer(object):
def __init__(self, filename):
self.lexers_ = [self.make_lexer_(filename, (filename, 0, 0))]
def __iter__(self):
return self
def next(self): # Python 2
return self.__next__()
def __next__(self): # Python 3
while self.lexers_:
lexer = self.lexers_[-1]
try:
token_type, token, location = lexer.next()
except StopIteration:
self.lexers_.pop()
continue
if token_type is Lexer.NAME and token == "include":
fname_type, fname_token, fname_location = lexer.next()
if fname_type is not Lexer.FILENAME:
raise LexerError("Expected file name", fname_location)
semi_type, semi_token, semi_location = lexer.next()
if semi_type is not Lexer.SYMBOL or semi_token != ";":
raise LexerError("Expected ';'", semi_location)
curpath, _ = os.path.split(lexer.filename_)
path = os.path.join(curpath, fname_token)
if len(self.lexers_) >= 5:
raise LexerError("Too many recursive includes",
fname_location)
self.lexers_.append(self.make_lexer_(path, fname_location))
continue
else:
return (token_type, token, location)
raise StopIteration()
@staticmethod
def make_lexer_(filename, location):
try:
with codecs.open(filename, "rb", "utf-8") as f:
return Lexer(f.read(), filename)
except IOError as err:
raise LexerError(str(err), location)

View File

@ -1,6 +1,7 @@
from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
from fontTools.feaLib.lexer import Lexer, LexerError
from fontTools.feaLib.lexer import IncludingLexer, Lexer, LexerError
import os
import unittest
@ -94,5 +95,40 @@ class LexerTest(unittest.TestCase):
self.assertEqual(lexer.pos_, 3)
class IncludingLexerTest(unittest.TestCase):
@staticmethod
def getpath(filename):
path, _ = os.path.split(__file__)
return os.path.join(path, "testdata", filename)
def test_include(self):
lexer = IncludingLexer(self.getpath("include4.fea"))
result = ['%s %s:%d' % (token, os.path.split(loc[0])[1], loc[1])
for _, token, loc in lexer]
self.assertEqual(result, [
"I4a include4.fea:1",
"I3a include3.fea:1",
"I2a include2.fea:1",
"I1a include1.fea:1",
"I0 include0.fea:1",
"I1b include1.fea:3",
"I2b include2.fea:3",
"I3b include3.fea:3",
"I4b include4.fea:3"
])
def test_include_limit(self):
lexer = IncludingLexer(self.getpath("include6.fea"))
self.assertRaises(LexerError, lambda: list(lexer))
def test_include_self(self):
lexer = IncludingLexer(self.getpath("includeself.fea"))
self.assertRaises(LexerError, lambda: list(lexer))
def test_include_missing_file(self):
lexer = IncludingLexer(self.getpath("includemissingfile.fea"))
self.assertRaises(LexerError, lambda: list(lexer))
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1 @@
I0

View File

@ -0,0 +1,3 @@
I1a
include(include0.fea);
I1b

View File

@ -0,0 +1,3 @@
I2a
include(include1.fea);
I2b

View File

@ -0,0 +1,4 @@
I3a
include(include2.fea);
I3b

View File

@ -0,0 +1,4 @@
I4a
include(include3.fea);
I4b

View File

@ -0,0 +1,3 @@
I5a
include(include4.fea);
I5b

View File

@ -0,0 +1,3 @@
I6a
include(include5.fea);
I6b

View File

@ -0,0 +1 @@
include(missingfile.fea);

View File

@ -0,0 +1 @@
include(includeself.fea);