blob: 7686c0d5093d31a8173615c0f9998033cad7fc76 [file] [log] [blame]
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See https://swift.org/LICENSE.txt for license information
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
"""
Argument types useful for enforcing data-integrity and form when parsing
arguments.
"""
import os.path
import re
import shlex
from . import ArgumentTypeError
__all__ = [
'CompilerVersion',
'BoolType',
'PathType',
'RegexType',
'ClangVersionType',
'SwiftVersionType',
'ShellSplitType',
]
# -----------------------------------------------------------------------------
class CompilerVersion(object):
"""Wrapper type around compiler version strings.
"""
def __init__(self, *components):
if len(components) == 1:
if isinstance(components[0], str):
components = components[0].split('.')
elif isinstance(components[0], (list, tuple)):
components = components[0]
if len(components) == 0:
raise ValueError('compiler version cannot be empty')
self.components = tuple(int(part) for part in components)
def __eq__(self, other):
return self.components == other.components
def __str__(self):
return '.'.join([str(part) for part in self.components])
# -----------------------------------------------------------------------------
def _repr(cls, args):
"""Helper function for implementing __repr__ methods on *Type classes.
"""
_args = []
for key, value in args.viewitems():
_args.append('{}={}'.format(key, repr(value)))
return '{}({})'.format(type(cls).__name__, ', '.join(_args))
class BoolType(object):
"""Argument type used to validate an input string as a bool-like type.
Callers are able to override valid true and false values.
"""
TRUE_VALUES = [True, 1, 'TRUE', 'True', 'true', '1']
FALSE_VALUES = [False, 0, 'FALSE', 'False', 'false', '0']
def __init__(self, true_values=None, false_values=None):
true_values = true_values or BoolType.TRUE_VALUES
false_values = false_values or BoolType.FALSE_VALUES
self._true_values = set(true_values)
self._false_values = set(false_values)
def __call__(self, value):
if value in self._true_values:
return True
elif value in self._false_values:
return False
else:
raise ArgumentTypeError('{} is not a boolean value'.format(value))
def __repr__(self):
return _repr(self, {
'true_values': self._true_values,
'false_values': self._false_values,
})
class PathType(object):
"""PathType denotes a valid path-like object. When called paths will be
fully expanded with the option to assert the file or directory referenced
by the path exists.
"""
def __init__(self, assert_exists=False, assert_executable=False):
self._assert_exists = assert_exists
self._assert_executable = assert_executable
def __call__(self, path):
path = os.path.expanduser(path)
path = os.path.abspath(path)
if self._assert_exists and not os.path.exists(path):
raise ArgumentTypeError('{} does not exist'.format(path))
if self._assert_executable and not PathType._is_executable(path):
raise ArgumentTypeError('{} is not an executable'.format(path))
return path
def __repr__(self):
return _repr(self, {
'assert_exists': self._assert_exists,
'assert_executable': self._assert_executable,
})
@staticmethod
def _is_executable(path):
return os.path.isfile(path) and os.access(path, os.X_OK)
class RegexType(object):
"""Argument type used to validate an input string against a regular
expression.
"""
def __init__(self, regex, error_message=None):
self._regex = regex
self._error_message = error_message or 'Invalid value'
def __call__(self, value):
matches = re.match(self._regex, value)
if matches is None:
raise ArgumentTypeError(self._error_message, value)
return matches
def __repr__(self):
return _repr(self, {
'regex': self._regex,
'error_message': self._error_message,
})
class ClangVersionType(RegexType):
"""Argument type used to validate Clang version strings.
"""
ERROR_MESSAGE = ('Invalid version value, must be '
'"MAJOR.MINOR.PATCH" or "MAJOR.MINOR.PATCH.PATCH"')
VERSION_REGEX = r'^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$'
def __init__(self):
super(ClangVersionType, self).__init__(
ClangVersionType.VERSION_REGEX,
ClangVersionType.ERROR_MESSAGE)
def __call__(self, value):
matches = super(ClangVersionType, self).__call__(value)
components = filter(lambda x: x is not None, matches.group(1, 2, 3, 5))
return CompilerVersion(components)
class SwiftVersionType(RegexType):
"""Argument type used to validate Swift version strings.
"""
ERROR_MESSAGE = ('Invalid version value, must be "MAJOR.MINOR" '
'or "MAJOR.MINOR.PATCH"')
VERSION_REGEX = r'^(\d+)\.(\d+)(\.(\d+))?$'
def __init__(self):
super(SwiftVersionType, self).__init__(
SwiftVersionType.VERSION_REGEX,
SwiftVersionType.ERROR_MESSAGE)
def __call__(self, value):
matches = super(SwiftVersionType, self).__call__(value)
components = filter(lambda x: x is not None, matches.group(1, 2, 4))
return CompilerVersion(components)
class ShellSplitType(object):
"""Parse and split shell arguments into a list of strings. Recognizes `,`
as a separator as well as white spaces.
For example it converts the following:
'-BAR="foo bar" -BAZ="foo,bar",-QUX 42'
into
['-BAR=foo bar', '-BAZ=foo,bar', '-QUX', '42']
"""
def __call__(self, value):
lex = shlex.shlex(value, posix=True)
lex.whitespace_split = True
lex.whitespace += ','
return list(lex)
def __repr__(self):
return _repr(self, {})