# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2022 FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * . *
# * *
# ***************************************************************************
""" Validators used for various line edits """
import keyword
from PySide.QtGui import (
QValidator,
)
# QRegularExpressionValidator was only added at the very end of the PySide
# development cycle, so make sure to support the older QRegExp version as well.
try:
from PySide.QtGui import (
QRegularExpressionValidator,
)
from PySide.QtCore import QRegularExpression
RegexWrapper = QRegularExpression
RegexValidatorWrapper = QRegularExpressionValidator
except ImportError:
QRegularExpressionValidator = None
QRegularExpression = None
from PySide.QtGui import (
QRegExpValidator,
)
from PySide.QtCore import QRegExp
RegexWrapper = QRegExp
RegexValidatorWrapper = QRegExpValidator
# pylint: disable=too-few-public-methods
class NameValidator(QValidator):
"""Simple validator to exclude characters that are not valid in filenames."""
invalid = '/\\?%*:|"<>'
def validate(self, value: str, _: int):
"""Check the value against the validator"""
for char in value:
if char in NameValidator.invalid:
return QValidator.Invalid
return QValidator.Acceptable
def fixup(self, value: str) -> str:
"""Remove invalid characters from value"""
result = ""
for char in value:
if char not in NameValidator.invalid:
result += char
return result
class PythonIdentifierValidator(QValidator):
"""Validates whether input is a valid Python identifier."""
def validate(self, value: str, _: int):
"""The function that does the validation."""
if not value:
return QValidator.Intermediate
if not value.isidentifier():
return QValidator.Invalid # Includes an illegal character of some sort
if keyword.iskeyword(value):
return QValidator.Intermediate # They can keep typing and it might become valid
return QValidator.Acceptable
class SemVerValidator(RegexValidatorWrapper):
"""Implements the officially-recommended regex validator for Semantic version numbers."""
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
semver_re = RegexWrapper(
r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)"
+ r"(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
+ r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
+ r"(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
)
def __init__(self):
super().__init__()
if hasattr(self, "setRegularExpression"):
self.setRegularExpression(SemVerValidator.semver_re)
else:
self.setRegExp(SemVerValidator.semver_re)
@classmethod
def check(cls, value: str) -> bool:
"""Returns true if value validates, and false if not"""
return cls.semver_re.match(value).hasMatch()
class CalVerValidator(RegexValidatorWrapper):
"""Implements a basic regular expression validator that makes sure an entry corresponds
to a CalVer version numbering standard."""
calver_re = RegexWrapper(
r"^(?P[1-9]\d{3})\.(?P[0-9]{1,2})\.(?P0|[0-9]{0,2})"
+ r"(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
+ r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
+ r"(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
)
def __init__(self):
super().__init__()
if hasattr(self, "setRegularExpression"):
self.setRegularExpression(CalVerValidator.calver_re)
else:
self.setRegExp(CalVerValidator.calver_re)
@classmethod
def check(cls, value: str) -> bool:
"""Returns true if value validates, and false if not"""
return cls.calver_re.match(value).hasMatch()
class VersionValidator(QValidator):
"""Implements the officially-recommended regex validator for Semantic version numbers, and a
decent approximation of the same thing for CalVer-style version numbers."""
def __init__(self):
super().__init__()
self.semver = SemVerValidator()
self.calver = CalVerValidator()
def validate(self, value: str, position: int):
"""Called for validation, returns a tuple of the validation state, the value, and the
position."""
semver_result = self.semver.validate(value, position)
calver_result = self.calver.validate(value, position)
if semver_result[0] == QValidator.Acceptable:
return semver_result
if calver_result[0] == QValidator.Acceptable:
return calver_result
if semver_result[0] == QValidator.Intermediate:
return semver_result
if calver_result[0] == QValidator.Intermediate:
return calver_result
return QValidator.Invalid, value, position