Ausgabe der neuen DB Einträge
This commit is contained in:
parent
bad48e1627
commit
cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions
380
venv/lib/python3.9/site-packages/stone/cli.py
Normal file
380
venv/lib/python3.9/site-packages/stone/cli.py
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
"""
|
||||
A command-line interface for StoneAPI.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import codecs
|
||||
import imp
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import six
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from .cli_helpers import parse_route_attr_filter
|
||||
from .compiler import (
|
||||
BackendException,
|
||||
Compiler,
|
||||
)
|
||||
from .frontend.exception import InvalidSpec
|
||||
from .frontend.frontend import specs_to_ir
|
||||
|
||||
_MYPY = False
|
||||
if _MYPY:
|
||||
import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression
|
||||
|
||||
# Hack to get around some of Python 2's standard library modules that
|
||||
# accept ascii-encodable unicode literals in lieu of strs, but where
|
||||
# actually passing such literals results in errors with mypy --py2. See
|
||||
# <https://github.com/python/typeshed/issues/756> and
|
||||
# <https://github.com/python/mypy/issues/2536>.
|
||||
import importlib
|
||||
argparse = importlib.import_module(str('argparse')) # type: typing.Any
|
||||
|
||||
# These backends come by default
|
||||
_builtin_backends = (
|
||||
'obj_c_client',
|
||||
'obj_c_types',
|
||||
'obj_c_tests',
|
||||
'js_client',
|
||||
'js_types',
|
||||
'tsd_client',
|
||||
'tsd_types',
|
||||
'python_types',
|
||||
'python_type_stubs',
|
||||
'python_client',
|
||||
'swift_types',
|
||||
'swift_client',
|
||||
)
|
||||
|
||||
# The parser for command line arguments
|
||||
_cmdline_description = (
|
||||
'Write your APIs in Stone. Use backends to translate your specification '
|
||||
'into a target language or format. The following describes arguments to '
|
||||
'the Stone CLI. To specify arguments that are specific to a backend, '
|
||||
'add "--" followed by arguments. For example, "stone python_client . '
|
||||
'example.spec -- -h".'
|
||||
)
|
||||
_cmdline_parser = argparse.ArgumentParser(description=_cmdline_description)
|
||||
_cmdline_parser.add_argument(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='count',
|
||||
help='Print debugging statements.',
|
||||
)
|
||||
_backend_help = (
|
||||
'Either the name of a built-in backend or the path to a backend '
|
||||
'module. Paths to backend modules must end with a .stoneg.py extension. '
|
||||
'The following backends are built-in: ' + ', '.join(_builtin_backends))
|
||||
_cmdline_parser.add_argument(
|
||||
'backend',
|
||||
type=six.text_type,
|
||||
help=_backend_help,
|
||||
)
|
||||
_cmdline_parser.add_argument(
|
||||
'output',
|
||||
type=six.text_type,
|
||||
help='The folder to save generated files to.',
|
||||
)
|
||||
_cmdline_parser.add_argument(
|
||||
'spec',
|
||||
nargs='*',
|
||||
type=six.text_type,
|
||||
help=('Path to API specifications. Each must have a .stone extension. '
|
||||
'If omitted or set to "-", the spec is read from stdin. Multiple '
|
||||
'namespaces can be provided over stdin by concatenating multiple '
|
||||
'specs together.'),
|
||||
)
|
||||
_cmdline_parser.add_argument(
|
||||
'--clean-build',
|
||||
action='store_true',
|
||||
help='The path to the template SDK for the target language.',
|
||||
)
|
||||
_cmdline_parser.add_argument(
|
||||
'-f',
|
||||
'--filter-by-route-attr',
|
||||
type=six.text_type,
|
||||
help=('Removes routes that do not match the expression. The expression '
|
||||
'must specify a route attribute on the left-hand side and a value '
|
||||
'on the right-hand side. Use quotes for strings and bytes. The only '
|
||||
'supported operators are "=" and "!=". For example, if "hide" is a '
|
||||
'route attribute, we can use this filter: "hide!=true". You can '
|
||||
'combine multiple expressions with "and"/"or" and use parentheses '
|
||||
'to enforce precedence.'),
|
||||
)
|
||||
_cmdline_parser.add_argument(
|
||||
'-r',
|
||||
'--route-whitelist-filter',
|
||||
type=six.text_type,
|
||||
help=('Restrict datatype generation to only the routes specified in the whitelist '
|
||||
'and their dependencies. Input should be a file containing a JSON dict with '
|
||||
'the following form: {"route_whitelist": {}, "datatype_whitelist": {}} '
|
||||
'where each object maps namespaces to lists of routes or datatypes to whitelist.'),
|
||||
)
|
||||
_cmdline_parser.add_argument(
|
||||
'-a',
|
||||
'--attribute',
|
||||
action='append',
|
||||
type=str,
|
||||
default=[],
|
||||
help=('Route attributes that the backend will have access to and '
|
||||
'presumably expose in generated code. Use ":all" to select all '
|
||||
'attributes defined in stone_cfg.Route. Note that you can filter '
|
||||
'(-f) by attributes that are not listed here.'),
|
||||
)
|
||||
_filter_ns_group = _cmdline_parser.add_mutually_exclusive_group()
|
||||
_filter_ns_group.add_argument(
|
||||
'-w',
|
||||
'--whitelist-namespace-routes',
|
||||
action='append',
|
||||
type=str,
|
||||
default=[],
|
||||
help='If set, backends will only see the specified namespaces as having routes.',
|
||||
)
|
||||
_filter_ns_group.add_argument(
|
||||
'-b',
|
||||
'--blacklist-namespace-routes',
|
||||
action='append',
|
||||
type=str,
|
||||
default=[],
|
||||
help='If set, backends will not see any routes for the specified namespaces.',
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""The entry point for the program."""
|
||||
|
||||
if '--' in sys.argv:
|
||||
cli_args = sys.argv[1:sys.argv.index('--')]
|
||||
backend_args = sys.argv[sys.argv.index('--') + 1:]
|
||||
else:
|
||||
cli_args = sys.argv[1:]
|
||||
backend_args = []
|
||||
|
||||
args = _cmdline_parser.parse_args(cli_args)
|
||||
debug = False
|
||||
if args.verbose is None:
|
||||
logging_level = logging.WARNING
|
||||
elif args.verbose == 1:
|
||||
logging_level = logging.INFO
|
||||
elif args.verbose == 2:
|
||||
logging_level = logging.DEBUG
|
||||
debug = True
|
||||
else:
|
||||
print('error: I can only be so garrulous, try -vv.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
logging.basicConfig(level=logging_level)
|
||||
|
||||
if args.spec and args.spec[0].startswith('+') and args.spec[0].endswith('.py'):
|
||||
# Hack: Special case for defining a spec in Python for testing purposes
|
||||
# Use this if you want to define a Stone spec using a Python module.
|
||||
# The module should should contain an api variable that references a
|
||||
# :class:`stone.api.Api` object.
|
||||
try:
|
||||
api = imp.load_source('api', args.api[0]).api # pylint: disable=redefined-outer-name
|
||||
except ImportError as e:
|
||||
print('error: Could not import API description due to:',
|
||||
e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
if args.spec:
|
||||
specs = []
|
||||
read_from_stdin = False
|
||||
for spec_path in args.spec:
|
||||
if spec_path == '-':
|
||||
read_from_stdin = True
|
||||
elif not spec_path.endswith('.stone'):
|
||||
print("error: Specification '%s' must have a .stone extension."
|
||||
% spec_path,
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif not os.path.exists(spec_path):
|
||||
print("error: Specification '%s' cannot be found." % spec_path,
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
with open(spec_path) as f:
|
||||
specs.append((spec_path, f.read()))
|
||||
if read_from_stdin and specs:
|
||||
print("error: Do not specify stdin and specification files "
|
||||
"simultaneously.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not args.spec or read_from_stdin:
|
||||
specs = []
|
||||
if debug:
|
||||
print('Reading specification from stdin.')
|
||||
|
||||
if six.PY2:
|
||||
UTF8Reader = codecs.getreader('utf8')
|
||||
sys.stdin = UTF8Reader(sys.stdin)
|
||||
stdin_text = sys.stdin.read()
|
||||
else:
|
||||
stdin_buffer = sys.stdin.buffer # pylint: disable=no-member,useless-suppression
|
||||
stdin_text = io.TextIOWrapper(stdin_buffer, encoding='utf-8').read()
|
||||
|
||||
parts = stdin_text.split('namespace')
|
||||
if len(parts) == 1:
|
||||
specs.append(('stdin.1', parts[0]))
|
||||
else:
|
||||
specs.append(
|
||||
('stdin.1', '%snamespace%s' % (parts.pop(0), parts.pop(0))))
|
||||
while parts:
|
||||
specs.append(('stdin.%s' % (len(specs) + 1),
|
||||
'namespace%s' % parts.pop(0)))
|
||||
|
||||
if args.filter_by_route_attr:
|
||||
route_filter, route_filter_errors = parse_route_attr_filter(
|
||||
args.filter_by_route_attr, debug)
|
||||
if route_filter_errors:
|
||||
print('Error(s) in route filter:', file=sys.stderr)
|
||||
for err in route_filter_errors:
|
||||
print(err, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
route_filter = None
|
||||
|
||||
if args.route_whitelist_filter:
|
||||
with open(args.route_whitelist_filter) as f:
|
||||
route_whitelist_filter = json.loads(f.read())
|
||||
else:
|
||||
route_whitelist_filter = None
|
||||
|
||||
try:
|
||||
# TODO: Needs version
|
||||
api = specs_to_ir(specs, debug=debug,
|
||||
route_whitelist_filter=route_whitelist_filter)
|
||||
except InvalidSpec as e:
|
||||
print('%s:%s: error: %s' % (e.path, e.lineno, e.msg), file=sys.stderr)
|
||||
if debug:
|
||||
print('A traceback is included below in case this is a bug in '
|
||||
'Stone.\n', traceback.format_exc(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if api is None:
|
||||
print('You must fix the above parsing errors for generation to '
|
||||
'continue.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if args.whitelist_namespace_routes:
|
||||
for namespace_name in args.whitelist_namespace_routes:
|
||||
if namespace_name not in api.namespaces:
|
||||
print('error: Whitelisted namespace missing from spec: %s' %
|
||||
namespace_name, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
for namespace in api.namespaces.values():
|
||||
if namespace.name not in args.whitelist_namespace_routes:
|
||||
namespace.routes = []
|
||||
namespace.route_by_name = {}
|
||||
namespace.routes_by_name = {}
|
||||
|
||||
if args.blacklist_namespace_routes:
|
||||
for namespace_name in args.blacklist_namespace_routes:
|
||||
if namespace_name not in api.namespaces:
|
||||
print('error: Blacklisted namespace missing from spec: %s' %
|
||||
namespace_name, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
namespace = api.namespaces[namespace_name]
|
||||
namespace.routes = []
|
||||
namespace.route_by_name = {}
|
||||
namespace.routes_by_name = {}
|
||||
|
||||
if route_filter:
|
||||
for namespace in api.namespaces.values():
|
||||
filtered_routes = []
|
||||
for route in namespace.routes:
|
||||
if route_filter.eval(route):
|
||||
filtered_routes.append(route)
|
||||
|
||||
namespace.routes = []
|
||||
namespace.route_by_name = {}
|
||||
namespace.routes_by_name = {}
|
||||
for route in filtered_routes:
|
||||
namespace.add_route(route)
|
||||
|
||||
if args.attribute:
|
||||
attrs = set(args.attribute)
|
||||
if ':all' in attrs:
|
||||
attrs = {field.name for field in api.route_schema.fields}
|
||||
else:
|
||||
attrs = set()
|
||||
|
||||
for namespace in api.namespaces.values():
|
||||
for route in namespace.routes:
|
||||
for k in list(route.attrs.keys()):
|
||||
if k not in attrs:
|
||||
del route.attrs[k]
|
||||
|
||||
# Remove attrs that weren't specified from the route schema
|
||||
for field in api.route_schema.fields[:]:
|
||||
if field.name not in attrs:
|
||||
api.route_schema.fields.remove(field)
|
||||
del api.route_schema._fields_by_name[field.name]
|
||||
else:
|
||||
attrs.remove(field.name)
|
||||
|
||||
# Error if specified attr isn't even a field in the route schema
|
||||
if attrs:
|
||||
attr = attrs.pop()
|
||||
print('error: Attribute not defined in stone_cfg.Route: %s' %
|
||||
attr, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if args.backend in _builtin_backends:
|
||||
backend_module = __import__(
|
||||
'stone.backends.%s' % args.backend, fromlist=[''])
|
||||
elif not os.path.exists(args.backend):
|
||||
print("error: Backend '%s' cannot be found." % args.backend,
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif not os.path.isfile(args.backend):
|
||||
print("error: Backend '%s' must be a file." % args.backend,
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif not Compiler.is_stone_backend(args.backend):
|
||||
print("error: Backend '%s' must have a .stoneg.py extension." %
|
||||
args.backend, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# A bit hacky, but we add the folder that the backend is in to our
|
||||
# python path to support the case where the backend imports other
|
||||
# files in its local directory.
|
||||
new_python_path = os.path.dirname(args.backend)
|
||||
if new_python_path not in sys.path:
|
||||
sys.path.append(new_python_path)
|
||||
try:
|
||||
backend_module = imp.load_source('user_backend', args.backend)
|
||||
except Exception:
|
||||
print("error: Importing backend '%s' module raised an exception:" %
|
||||
args.backend, file=sys.stderr)
|
||||
raise
|
||||
|
||||
c = Compiler(
|
||||
api,
|
||||
backend_module,
|
||||
backend_args,
|
||||
args.output,
|
||||
clean_build=args.clean_build,
|
||||
)
|
||||
try:
|
||||
c.build()
|
||||
except BackendException as e:
|
||||
print('%s: error: %s raised an exception:\n%s' %
|
||||
(args.backend, e.backend_name, e.traceback),
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not sys.argv[0].endswith('stone'):
|
||||
# If we aren't running from an entry_point, then return api to make it
|
||||
# easier to do debugging.
|
||||
return api
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Assign api variable for easy debugging from a Python console
|
||||
api = main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue