243 lines
7.7 KiB
Python
243 lines
7.7 KiB
Python
|
from collections import defaultdict
|
||
|
from datetime import datetime
|
||
|
from itertools import chain
|
||
|
import os.path
|
||
|
import sys
|
||
|
|
||
|
from glad.lang.common.loader import NullLoader
|
||
|
from glad.opener import URLOpener
|
||
|
from glad.util import api_name
|
||
|
import glad
|
||
|
|
||
|
|
||
|
if sys.version_info >= (3, 0):
|
||
|
from urllib.parse import urlencode
|
||
|
else:
|
||
|
from urllib import urlencode
|
||
|
|
||
|
|
||
|
HEADER_TEMPLATE = '''
|
||
|
{apis_named} loader generated by glad {version} on {date}.
|
||
|
|
||
|
Language/Generator: {language}
|
||
|
Specification: {specification}
|
||
|
APIs: {apis}
|
||
|
Profile: {profile}
|
||
|
Extensions:
|
||
|
{extensions}
|
||
|
Loader: {loader}
|
||
|
Local files: {local_files}
|
||
|
Omit khrplatform: {omit_khrplatform}
|
||
|
|
||
|
Commandline:
|
||
|
{commandline}
|
||
|
Online:
|
||
|
{online}
|
||
|
'''
|
||
|
|
||
|
|
||
|
class Generator(object):
|
||
|
NAME = None
|
||
|
NAME_LONG = None
|
||
|
URL = 'http://glad.dav1d.de'
|
||
|
|
||
|
def __init__(self, path, spec, api, extension_names=None, loader=None,
|
||
|
opener=None, local_files=False, omit_khrplatform=False,
|
||
|
header_template=HEADER_TEMPLATE):
|
||
|
self.path = os.path.abspath(path)
|
||
|
|
||
|
self.spec = spec
|
||
|
for a in api:
|
||
|
if a not in self.spec.features:
|
||
|
raise ValueError(
|
||
|
'Unknown API "{0}" for specification "{1}"'
|
||
|
.format(a, self.spec.NAME)
|
||
|
)
|
||
|
self.api = api
|
||
|
self.extension_names = extension_names
|
||
|
|
||
|
self.has_loader = not loader.disabled
|
||
|
self.loader = loader
|
||
|
if self.loader is None:
|
||
|
self.loader = NullLoader
|
||
|
|
||
|
self.opener = opener
|
||
|
if self.opener is None:
|
||
|
self.opener = URLOpener.default()
|
||
|
|
||
|
self.local_files = local_files
|
||
|
self.omit_khrplatform = omit_khrplatform
|
||
|
|
||
|
self._header_template = header_template
|
||
|
|
||
|
def open(self):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def close(self):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def __enter__(self):
|
||
|
self.open()
|
||
|
return self
|
||
|
|
||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||
|
self.close()
|
||
|
|
||
|
def generate(self):
|
||
|
features = list()
|
||
|
for api, version in self.api.items():
|
||
|
features.extend(self.spec.features[api])
|
||
|
|
||
|
if version is None:
|
||
|
version = list(self.spec.features[api].keys())[-1]
|
||
|
self.api[api] = version
|
||
|
|
||
|
if version not in self.spec.features[api]:
|
||
|
raise ValueError(
|
||
|
'Unknown version "{0}" for specification "{1}"'
|
||
|
.format(version, self.spec.NAME)
|
||
|
)
|
||
|
|
||
|
if self.extension_names is None:
|
||
|
self.extension_names = list(chain.from_iterable(self.spec.extensions[a]
|
||
|
for a in self.api))
|
||
|
|
||
|
# sort and eliminate duplicates
|
||
|
self.extension_names = list(sorted(set(self.extension_names)))
|
||
|
|
||
|
e = list(chain.from_iterable(self.spec.extensions[a] for a in self.api))
|
||
|
for ext in self.extension_names:
|
||
|
if ext not in e:
|
||
|
raise ValueError(
|
||
|
'Invalid extension "{0}" for specification "{1}"'
|
||
|
.format(ext, self.spec.NAME)
|
||
|
)
|
||
|
|
||
|
self.generate_header()
|
||
|
|
||
|
types = [t for t in self.spec.types if t.api is None or t.api in self.api]
|
||
|
self.generate_types(types)
|
||
|
|
||
|
f = list()
|
||
|
for api, version in self.api.items():
|
||
|
f.extend([value for key, value in self.spec.features[api].items()
|
||
|
if key <= version])
|
||
|
enums, functions = merge(f)
|
||
|
self.generate_features(f)
|
||
|
|
||
|
extensions = list()
|
||
|
for api in self.api:
|
||
|
extensions.extend(self.spec.extensions[api][ext]
|
||
|
for ext in self.extension_names if ext
|
||
|
in self.spec.extensions[api])
|
||
|
self.generate_extensions(extensions, enums, functions)
|
||
|
|
||
|
fs = defaultdict(list)
|
||
|
es = defaultdict(list)
|
||
|
for api, version in self.api.items():
|
||
|
fs[api].extend(
|
||
|
[value for key, value in
|
||
|
self.spec.features[api].items() if key <= version]
|
||
|
)
|
||
|
es[api].extend(self.spec.extensions[api][ext]
|
||
|
for ext in self.extension_names if ext
|
||
|
in self.spec.extensions[api])
|
||
|
self.generate_loader(fs, es)
|
||
|
|
||
|
@property
|
||
|
def header(self):
|
||
|
apis_named = ', '.join(sorted(set(api_name(api) for api in self.api)))
|
||
|
date = datetime.now().strftime('%c')
|
||
|
language = self.NAME_LONG
|
||
|
specification = self.spec.NAME
|
||
|
apis = ', '.join('{}={}'.format(api, '.'.join(map(str, version))) for api, version in self.api.items())
|
||
|
profile = getattr(self.spec, 'profile', '-')
|
||
|
extensions = ',\n '.join(self.extension_names)
|
||
|
online = self.online
|
||
|
if len(online) > 2000:
|
||
|
online = 'Too many extensions'
|
||
|
|
||
|
return self._header_template.format(
|
||
|
apis_named=apis_named,
|
||
|
version=glad.__version__,
|
||
|
date=date,
|
||
|
language=language,
|
||
|
specification=specification,
|
||
|
apis=apis,
|
||
|
profile=profile,
|
||
|
extensions=extensions,
|
||
|
loader=self.has_loader,
|
||
|
local_files=self.local_files,
|
||
|
omit_khrplatform=self.omit_khrplatform,
|
||
|
commandline=self.commandline,
|
||
|
online=online
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def commandline(self):
|
||
|
profile = getattr(self.spec, 'profile', None)
|
||
|
if profile is not None:
|
||
|
profile = '--profile="{}"'.format(profile)
|
||
|
|
||
|
api = '--api="{}"'.format(','.join(
|
||
|
'{}={}'.format(api, '.'.join(map(str, version))) for api, version in self.api.items())
|
||
|
)
|
||
|
generator = '--generator="{}"'.format(self.NAME)
|
||
|
specification = '--spec="{}"'.format(self.spec.NAME)
|
||
|
loader = '' if self.has_loader else '--no-loader'
|
||
|
extensions = '--extensions="{}"'.format(','.join(self.extension_names))
|
||
|
local_files = '--local-files' if self.local_files else ''
|
||
|
omit_khrplatform = '--omit-khrplatform' if self.omit_khrplatform else ''
|
||
|
|
||
|
return ' '.join(filter(None, [
|
||
|
profile, api, generator, specification,
|
||
|
loader, local_files, omit_khrplatform, extensions
|
||
|
]))
|
||
|
|
||
|
@property
|
||
|
def online(self):
|
||
|
profile = getattr(self.spec, 'profile', None)
|
||
|
if profile is not None:
|
||
|
profile = ('profile', profile)
|
||
|
|
||
|
api = [('api', s) for s in ('{}={}'.format(api, '.'.join(map(str, version))) for api, version in self.api.items())]
|
||
|
generator = ('language', self.NAME)
|
||
|
specification = ('specification', self.spec.NAME)
|
||
|
loader = ('loader', 'on') if self.has_loader else None
|
||
|
extensions = [('extensions', ext) for ext in self.extension_names]
|
||
|
|
||
|
data = [profile, generator, specification, loader]
|
||
|
data.extend(api)
|
||
|
data.extend(extensions)
|
||
|
data = list(filter(None, data))
|
||
|
serialized = urlencode(data)
|
||
|
|
||
|
# TODO: --local-files, --omit-khrplatform
|
||
|
return '{}/#{}'.format(self.URL, serialized)
|
||
|
|
||
|
def generate_header(self):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def generate_loader(self, features, extensions):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def generate_types(self, types):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def generate_features(self, features):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def generate_extensions(self, extensions, enums, functions):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
|
||
|
def merge(features):
|
||
|
enums = set()
|
||
|
functions = set()
|
||
|
|
||
|
for feature in features:
|
||
|
enums |= set(feature.enums)
|
||
|
functions |= set(feature.functions)
|
||
|
|
||
|
return enums, functions
|