Handle inclusion of OpenType feature files
This commit is contained in:
parent
ac700b0af5
commit
efbcba79a4
@ -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)
|
||||
|
@ -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()
|
||||
|
1
Lib/fontTools/feaLib/testdata/include0.fea
vendored
Normal file
1
Lib/fontTools/feaLib/testdata/include0.fea
vendored
Normal file
@ -0,0 +1 @@
|
||||
I0
|
3
Lib/fontTools/feaLib/testdata/include1.fea
vendored
Normal file
3
Lib/fontTools/feaLib/testdata/include1.fea
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
I1a
|
||||
include(include0.fea);
|
||||
I1b
|
3
Lib/fontTools/feaLib/testdata/include2.fea
vendored
Normal file
3
Lib/fontTools/feaLib/testdata/include2.fea
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
I2a
|
||||
include(include1.fea);
|
||||
I2b
|
4
Lib/fontTools/feaLib/testdata/include3.fea
vendored
Normal file
4
Lib/fontTools/feaLib/testdata/include3.fea
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
I3a
|
||||
include(include2.fea);
|
||||
I3b
|
||||
|
4
Lib/fontTools/feaLib/testdata/include4.fea
vendored
Normal file
4
Lib/fontTools/feaLib/testdata/include4.fea
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
I4a
|
||||
include(include3.fea);
|
||||
I4b
|
||||
|
3
Lib/fontTools/feaLib/testdata/include5.fea
vendored
Normal file
3
Lib/fontTools/feaLib/testdata/include5.fea
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
I5a
|
||||
include(include4.fea);
|
||||
I5b
|
3
Lib/fontTools/feaLib/testdata/include6.fea
vendored
Normal file
3
Lib/fontTools/feaLib/testdata/include6.fea
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
I6a
|
||||
include(include5.fea);
|
||||
I6b
|
1
Lib/fontTools/feaLib/testdata/includemissingfile.fea
vendored
Normal file
1
Lib/fontTools/feaLib/testdata/includemissingfile.fea
vendored
Normal file
@ -0,0 +1 @@
|
||||
include(missingfile.fea);
|
1
Lib/fontTools/feaLib/testdata/includeself.fea
vendored
Normal file
1
Lib/fontTools/feaLib/testdata/includeself.fea
vendored
Normal file
@ -0,0 +1 @@
|
||||
include(includeself.fea);
|
Loading…
x
Reference in New Issue
Block a user