diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 6f11ab1ef..4c571fcb3 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -134,11 +134,14 @@ class BaseType: """Get the type's valid values for documentation.""" return self.valid_values - def _basic_validation(self, value): + def _basic_validation(self, value, pytype=None): """Do some basic validation for the value (empty, non-printable chars). + Also does a Python typecheck on the value (if coming from YAML/Python). + Arguments: value: The value to check. + pytype: If given, a Python type to check the value against. """ if not value: if self.none_ok: @@ -146,29 +149,17 @@ class BaseType: else: raise configexc.ValidationError(value, "may not be empty!") - if any(ord(c) < 32 or ord(c) == 0x7f for c in value): - raise configexc.ValidationError(value, "may not contain " - "unprintable chars!") + if isinstance(value, str): + if any(ord(c) < 32 or ord(c) == 0x7f for c in value): + raise configexc.ValidationError(value, "may not contain " + "unprintable chars!") - def transform(self, value): - """Transform the setting value. + if pytype is not None and not isinstance(value, pytype): + raise configexc.ValidationError( + value, "expected a value of type {} but got {}".format( + pytype, type(value))) - This method can assume the value is indeed a valid value. - - The default implementation returns the original value. - - Args: - value: The original string value. - - Return: - The transformed value. - """ - if not value: - return None - else: - return value - - def validate(self, value): + def _validate_valid_values(self, value): """Validate value against possible values. The default implementation checks the value against self.valid_values @@ -177,7 +168,7 @@ class BaseType: Args: value: The value to validate. """ - self._basic_validation(value) + # FIXME:conf still needed? if not value: return if self.valid_values is not None: @@ -189,6 +180,45 @@ class BaseType: raise NotImplementedError("{} does not implement validate.".format( self.__class__.__name__)) + def from_str(self, value): + """Get the setting value from a string. + + Args: + value: The original string value. + + Return: + The transformed value. + """ + raise NotImplementedError + + def from_py(self, value): + """Get the setting value from a Python value. + + Args: + value: The value we got from Python/YAML. + + Return: + The transformed value. + + Raise: + configexc.ValidationError if the value was invalid. + """ + raise NotImplementedError + + def to_str(self, value): + """Get a string from the setting value. + + The resulting string should be parseable again by from_str. + """ + raise NotImplementedError + + # def to_py(self, value): + # """Get a Python/YAML value from the setting value. + + # The resulting value should be parseable again by from_py. + # """ + # raise NotImplementedError + def complete(self): """Return a list of possible values for completion. @@ -223,19 +253,25 @@ class MappingType(BaseType): MAPPING = {} - def __init__(self, none_ok=False, - valid_values=None): + def __init__(self, none_ok=False, valid_values=None): super().__init__(none_ok) self.valid_values = valid_values - def validate(self, value): - super().validate(value.lower()) - - def transform(self, value): + def from_py(self, value): + self._basic_validation(value, pytype=str) if not value: return None + self._validate_valid_values(value.lower()) return self.MAPPING[value.lower()] + def from_str(self, value): + return self.from_py(value) + + def to_str(self, value): + reverse_mapping = {v: k for k, v in self.MAPPING.items()} + assert len(self.MAPPING) == len(reverse_mapping) + return reverse_mapping[value] + class String(BaseType): @@ -265,16 +301,11 @@ class String(BaseType): self.forbidden = forbidden self._completions = completions - def validate(self, value): - self._basic_validation(value) + def from_py(self, value): + self._basic_validation(value, pytype=str) if not value: - return - - if self.valid_values is not None: - if value not in self.valid_values: - raise configexc.ValidationError( - value, - "valid values: {}".format(', '.join(self.valid_values))) + return None + self._validate_valid_values(value) if self.forbidden is not None and any(c in value for c in self.forbidden): @@ -287,6 +318,11 @@ class String(BaseType): raise configexc.ValidationError(value, "must be at most {} chars " "long!".format(self.maxlen)) + return value + + def from_str(self, value): + return self.from_py(value) + def complete(self): if self._completions is not None: return self._completions @@ -298,16 +334,18 @@ class UniqueCharString(String): """A string which may not contain duplicate chars.""" - def validate(self, value): - super().validate(value) + def from_py(self, value): + value = super().from_py(value) if not value: - return + return None # Check for duplicate values if len(set(value)) != len(value): raise configexc.ValidationError( value, "String contains duplicate values!") + return value + class List(BaseType): @@ -329,23 +367,23 @@ class List(BaseType): def get_valid_values(self): return self.valtype.get_valid_values() - def transform(self, value): + def from_str(self, value): + try: + json_val = json.loads(value) + except ValueError as e: + raise configexc.ValidationError(value, str(e)) + return self.from_py(json_val) + + def from_py(self, value): + self._basic_validation(value, pytype=list) if not value: return None - else: - return [self.valtype.transform(v.strip()) - for v in value.split(',')] - def validate(self, value): - self._basic_validation(value) - if not value: - return - vals = value.split(',') - if self.length is not None and len(vals) != self.length: + if self.length is not None and len(value) != self.length: raise configexc.ValidationError(value, "Exactly {} values need to " "be set!".format(self.length)) - for val in vals: - self.valtype.validate(val.strip()) + + return [self.valtype.from_py(v) for v in value] class FlagList(List): @@ -364,19 +402,13 @@ class FlagList(List): super().__init__(BaseType(), none_ok) self.valtype.valid_values = valid_values - def validate(self, value): - if self.valtype.valid_values is not None: - super().validate(value) - else: - self._basic_validation(value) - if not value: - return - vals = super().transform(value) - + def from_py(self, value): + vals = super().from_py(value) # Check for duplicate values if len(set(vals)) != len(vals): raise configexc.ValidationError( value, "List contains duplicate values!") + return vals def complete(self): valid_values = self.valtype.valid_values @@ -407,17 +439,18 @@ class Bool(BaseType): super().__init__(none_ok) self.valid_values = ValidValues('true', 'false') - def transform(self, value): - if not value: - return None - else: - return BOOLEAN_STATES[value.lower()] + def from_py(self, value): + self._basic_validation(value, pytype=bool) + return value - def validate(self, value): + def from_str(self, value): self._basic_validation(value) if not value: - return - elif value.lower() not in BOOLEAN_STATES: + return None + + try: + return BOOLEAN_STATES[value.lower()] + except KeyError: raise configexc.ValidationError(value, "must be a boolean!") @@ -429,17 +462,15 @@ class BoolAsk(Bool): super().__init__(none_ok) self.valid_values = ValidValues('true', 'false', 'ask') - def transform(self, value): - if value.lower() == 'ask': + def from_py(self, value): + # basic validation unneeded if it's == 'ask' and done by Bool if we call + # super().from_py + if isinstance(value, str) and value.lower() == 'ask': return 'ask' - else: - return super().transform(value) + return super().from_py(value) - def validate(self, value): - if value.lower() == 'ask': - return - else: - super().validate(value) + def from_str(self, value): + return self.from_py(value) class Int(BaseType): @@ -470,26 +501,24 @@ class Int(BaseType): assert isinstance(value, int), value return value - def transform(self, value): - if not value: - return None - else: - return int(value) - - def validate(self, value): - self._basic_validation(value) - if not value: - return + def from_str(self, value): try: intval = int(value) except ValueError: raise configexc.ValidationError(value, "must be an integer!") - if self.minval is not None and intval < self.minval: + return self.from_py(intval) + + def from_py(self, value): + self._basic_validation(value, pytype=int) + if not value: + return value + if self.minval is not None and value < self.minval: raise configexc.ValidationError(value, "must be {} or " "bigger!".format(self.minval)) - if self.maxval is not None and intval > self.maxval: + if self.maxval is not None and value > self.maxval: raise configexc.ValidationError(value, "must be {} or " "smaller!".format(self.maxval)) + return value class Float(BaseType): @@ -501,34 +530,46 @@ class Float(BaseType): maxval: Maximum value (inclusive). """ + # FIXME:conf inherit Int/Float + def __init__(self, minval=None, maxval=None, none_ok=False): super().__init__(none_ok) - if maxval is not None and minval is not None and maxval < minval: - raise ValueError("minval ({}) needs to be <= maxval ({})!".format( - minval, maxval)) - self.minval = minval - self.maxval = maxval + self.minval = self._parse_limit(minval) + self.maxval = self._parse_limit(maxval) + if self.maxval is not None and self.minval is not None: + if self.maxval < self.minval: + raise ValueError("minval ({}) needs to be <= maxval ({})!" + .format(self.minval, self.maxval)) - def transform(self, value): - if not value: - return None + def _parse_limit(self, value): + if value == 'maxint': + return qtutils.MAXVALS['int'] + elif value == 'maxint64': + return qtutils.MAXVALS['int64'] else: - return float(value) + if value is not None: + assert isinstance(value, int), value + return value - def validate(self, value): - self._basic_validation(value) - if not value: - return + def from_str(self, value): try: - floatval = float(value) + intval = int(value) except ValueError: - raise configexc.ValidationError(value, "must be a float!") - if self.minval is not None and floatval < self.minval: + raise configexc.ValidationError(value, "must be an integer!") + return self.from_py(intval) + + def from_py(self, value): + self._basic_validation(value, pytype=int) + if not value: + return value + if self.minval is not None and value < self.minval: raise configexc.ValidationError(value, "must be {} or " "bigger!".format(self.minval)) - if self.maxval is not None and floatval > self.maxval: + if self.maxval is not None and value > self.maxval: raise configexc.ValidationError(value, "must be {} or " "smaller!".format(self.maxval)) + return value + class Perc(BaseType):