# ==================================================================================================================== #
# _____ ____ _ _ ___ ______ ____ ____ __ #
# _ __ _ _| ____| _ \ / \ / \ / _ \/ ___\ \ / /\ \ / / \/ | #
# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | \___ \\ \ / / \ \ / /| |\/| | #
# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| |___) |\ V / \ V / | | | | #
# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/|____/ \_/ \_/ |_| |_| #
# |_| |___/ #
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# License: #
# ==================================================================================================================== #
# Copyright 2025-2025 Patrick Lehmann - Boetzingen, Germany #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
#
from pathlib import Path
from typing import Optional as Nullable, List, Dict, Mapping, Iterable, TypeVar, Generic, Generator
from pyTooling.Common import getFullyQualifiedName
from pyTooling.Decorators import readonly, export
from pyTooling.MetaClasses import ExtendedType
from pyVHDLModel import VHDLVersion
from pyEDAA.OSVVM import OSVVMException
__all__ = ["osvvmContext"]
_ParentType = TypeVar("_ParentType", bound="Base")
[docs]
@export
class Base(Generic[_ParentType], metaclass=ExtendedType, slots=True):
_parent: Nullable[_ParentType]
[docs]
def __init__(self, parent: Nullable[_ParentType] = None):
self._parent = parent
@readonly
def Parent(self) -> _ParentType:
return self._parent
[docs]
@export
class Named(Base[_ParentType], Generic[_ParentType]):
_name: str
[docs]
def __init__(
self,
name: str,
parent: Nullable[_ParentType] = None
) -> None:
super().__init__(parent)
if not isinstance(name, str): # pragma: no cover
ex = TypeError(f"Parameter 'name' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
raise ex
self._name = name
@readonly
def Name(self) -> str:
return self._name
[docs]
def __repr__(self) -> str:
return f"{self.__class__.__name__}: {self._name}"
[docs]
@export
class Option(metaclass=ExtendedType, slots=True):
pass
[docs]
@export
class NoNullRangeWarning(Option):
[docs]
def __init__(self) -> None:
super().__init__()
[docs]
def __repr__(self) -> str:
return "NoNullRangeWarning"
[docs]
@export
class SourceFile(Base[_ParentType], Generic[_ParentType]):
"""A base-class describing any source file (VHDL, Verilog, ...) supported by OSVVM Scripts."""
_path: Path
[docs]
def __init__(
self,
path: Path,
parent: Nullable[Base] = None
) -> None:
super().__init__(parent)
if not isinstance(path, Path): # pragma: no cover
ex = TypeError(f"Parameter 'path' is not a Path.")
ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
raise ex
self._path = path
@readonly
def Path(self) -> Path:
"""
Read-only property to access the path to the sourcefile.
:returns: The sourcefile's path.
"""
return self._path
[docs]
def __repr__(self) -> str:
return f"SourceFile: {self._path}"
[docs]
@export
class VHDLSourceFile(SourceFile["VHDLLibrary"]):
_vhdlVersion: VHDLVersion
_noNullRangeWarning: Nullable[bool]
[docs]
def __init__(
self,
path: Path,
vhdlVersion: VHDLVersion = VHDLVersion.VHDL2008,
vhdlLibrary: Nullable["VHDLLibrary"] = None,
noNullRangeWarning: Nullable[bool] = None
):
if vhdlLibrary is None:
super().__init__(path, None)
elif isinstance(vhdlLibrary, VHDLLibrary):
super().__init__(path, vhdlLibrary)
vhdlLibrary._files.append(self)
else: # pragma: no cover
ex = TypeError(f"Parameter 'vhdlLibrary' is not a Library.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
raise ex
if not isinstance(vhdlVersion, VHDLVersion): # pragma: no cover
ex = TypeError(f"Parameter 'vhdlVersion' is not a VHDLVersion.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlVersion)}'.")
raise ex
self._vhdlVersion = vhdlVersion
if noNullRangeWarning is not None and not isinstance(noNullRangeWarning, bool):
ex = TypeError(f"Parameter 'noNullRangeWarning' is not a boolean.")
ex.add_note(f"Got type '{getFullyQualifiedName(noNullRangeWarning)}'.")
raise ex
self._noNullRangeWarning = noNullRangeWarning
@readonly
def VHDLLibrary(self) -> Nullable["VHDLLibrary"]:
return self._parent
@property
def VHDLVersion(self) -> VHDLVersion:
return self._vhdlVersion
@VHDLVersion.setter
def VHDLVersion(self, value: VHDLVersion) -> None:
if not isinstance(value, VHDLVersion):
ex = TypeError(f"Parameter 'value' is not a VHDLVersion.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
self._vhdlVersion = value
@property
def NoNullRangeWarning(self) -> bool:
return self._noNullRangeWarning
@NoNullRangeWarning.setter
def NoNullRangeWarning(self, value: bool) -> None:
if value is not None and not isinstance(value, bool):
ex = TypeError(f"Parameter 'value' is not a boolean.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
self._noNullRangeWarning = value
[docs]
def __repr__(self) -> str:
options = ""
if self._noNullRangeWarning is not None:
options += f", NoNullRangeWarning"
return f"VHDLSourceFile: {self._path} ({self._vhdlVersion}{options})"
[docs]
@export
class VHDLLibrary(Named["Build"]):
"""A VHDL library collecting multiple VHDL files containing VHDL design units."""
_files: List[VHDLSourceFile]
[docs]
def __init__(
self,
name: str,
vhdlFiles: Nullable[Iterable[VHDLSourceFile]] = None,
build: Nullable["Build"] = None
) -> None:
if build is None:
super().__init__(name, None)
elif isinstance(build, Build):
super().__init__(name, build)
build._vhdlLibraries[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'build' is not a Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
raise ex
self._files = []
if vhdlFiles is None:
pass
elif isinstance(vhdlFiles, Iterable):
for vhdlFile in vhdlFiles:
vhdlFile._parent = self
self._files.append(vhdlFile)
else: # pragma: no cover
ex = TypeError(f"Parameter 'vhdlFiles' is not an iterable of VHDLSourceFile.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlFiles)}'.")
raise ex
@readonly
def Build(self) -> Nullable["Build"]:
return self._parent
@readonly
def Files(self) -> List[SourceFile]:
return self._files
def AddFile(self, file: VHDLSourceFile) -> None:
if not isinstance(file, VHDLSourceFile): # pragma: no cover
ex = TypeError(f"Parameter 'file' is not a VHDLSourceFile.")
ex.add_note(f"Got type '{getFullyQualifiedName(file)}'.")
raise ex
file._parent = self
self._files.append(file)
[docs]
def __repr__(self) -> str:
return f"VHDLLibrary: {self._name}"
[docs]
@export
class GenericValue(Option):
_name: str
_value: str
[docs]
def __init__(
self,
name: str,
value: str
) -> None:
super().__init__()
if not isinstance(name, str): # pragma: no cover
ex = TypeError(f"Parameter 'name' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
raise ex
self._name = name
if not isinstance(value, str): # pragma: no cover
ex = TypeError(f"Parameter 'value' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
raise ex
self._value = value
@readonly
def Name(self) -> str:
return self._name
@readonly
def Value(self) -> str:
return self._value
[docs]
def __repr__(self) -> str:
return f"{self._name} = {self._value}"
[docs]
@export
class Testcase(Named["Testsuite"]):
_toplevelName: Nullable[str]
_generics: Dict[str, str]
[docs]
def __init__(
self,
name: str,
toplevelName: Nullable[str] = None,
generics: Nullable[Iterable[GenericValue] | Mapping[str, str]] = None,
testsuite: Nullable["Testsuite"] = None
) -> None:
if testsuite is None:
super().__init__(name, None)
elif isinstance(testsuite, Testsuite):
super().__init__(name, testsuite)
testsuite._testcases[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'testsuite' is not a Testsuite.")
ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.")
raise ex
if not (toplevelName is None or isinstance(toplevelName, str)): # pragma: no cover
ex = TypeError(f"Parameter 'toplevelName' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.")
raise ex
self._toplevelName = toplevelName
self._generics = {}
if generics is None:
pass
elif isinstance(generics, Mapping):
for key, value in generics.items():
self._generics[key] = value
elif isinstance(generics, Iterable):
for item in generics:
self._generics[item._name] = item._value
else: # pragma: no cover
ex = TypeError(f"Parameter 'generics' is not an iterable of GenericValue nor a dictionary of strings.")
ex.add_note(f"Got type '{getFullyQualifiedName(generics)}'.")
raise ex
@readonly
def Testsuite(self) -> "Testsuite":
return self._parent
@readonly
def ToplevelName(self) -> str:
return self._toplevelName
@readonly
def Generics(self) -> Dict[str, str]:
return self._generics
def SetToplevel(self, toplevelName: str) -> None:
if not isinstance(toplevelName, str): # pragma: no cover
ex = TypeError(f"Parameter 'toplevelName' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.")
raise ex
self._toplevelName = toplevelName
def AddGeneric(self, genericValue: GenericValue):
if not isinstance(genericValue, GenericValue): # pragma: no cover
ex = TypeError(f"Parameter 'genericValue' is not a GenericValue.")
ex.add_note(f"Got type '{getFullyQualifiedName(genericValue)}'.")
raise ex
self._generics[genericValue._name] = genericValue._value
[docs]
def __repr__(self) -> str:
generics = f" - [{', '.join([f'{n}={v}' for n,v in self._generics.items()])}]" if len(self._generics) > 0 else ""
return f"Testcase: {self._name}{generics}"
[docs]
@export
class Testsuite(Named["Build"]):
_testcases: Dict[str, Testcase]
[docs]
def __init__(
self,
name: str,
testcases: Nullable[Iterable[Testcase] | Mapping[str, Testcase]] = None,
build: Nullable["Build"] = None
) -> None:
if build is None:
super().__init__(name, None)
elif isinstance(build, Build):
super().__init__(name, build)
build._testsuites[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'build' is not a Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
raise ex
self._testcases = {}
if testcases is None:
pass
elif isinstance(testcases, Mapping):
for key, value in testcases.items():
value._parent = self
self._testcases[key] = value
elif isinstance(testcases, Iterable):
for item in testcases:
item._parent = self
self._testcases[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'testcases' is not an iterable of Testcase nor a mapping of Testcase.")
ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.")
raise ex
@readonly
def Build(self) -> Nullable["Build"]:
return self._parent
@readonly
def Testcases(self) -> Dict[str, Testcase]:
return self._testcases
def AddTestcase(self, testcase: Testcase) -> None:
if not isinstance(testcase, Testcase): # pragma: no cover
ex = TypeError(f"Parameter 'testcase' is not a Testcase.")
ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.")
raise ex
testcase._parent = self
self._testcases[testcase._name] = testcase
[docs]
def __repr__(self) -> str:
return f"Testsuite: {self._name}"
[docs]
@export
class BuildName(Option):
_name: str
[docs]
def __init__(
self,
name: str,
) -> None:
super().__init__()
if not isinstance(name, str): # pragma: no cover
ex = TypeError(f"Parameter 'name' is not a string.")
ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.")
raise ex
self._name = name
@readonly
def Name(self) -> str:
return self._name
[docs]
def __repr__(self) -> str:
return f"BuildName: {self._name}"
[docs]
@export
class Build(Named["Project"]):
_includedFiles: List[Path]
_vhdlLibraries: Dict[str, VHDLLibrary]
_testsuites: Dict[str, Testsuite]
[docs]
def __init__(
self,
name: str,
vhdlLibraries: Nullable[Iterable[VHDLLibrary] | Mapping[str, VHDLLibrary]] = None,
testsuites: Nullable[Iterable[Testsuite] | Mapping[str, Testsuite]] = None,
project: Nullable[Base] = None
) -> None:
if project is None:
super().__init__(name, None)
elif isinstance(project, Project):
super().__init__(name, project)
project._builds[name] = self
else: # pragma: no cover
ex = TypeError(f"Parameter 'project' is not a Project.")
ex.add_note(f"Got type '{getFullyQualifiedName(project)}'.")
raise ex
self._includedFiles = []
self._vhdlLibraries = {}
if vhdlLibraries is None:
pass
elif isinstance(vhdlLibraries, Mapping):
for key, value in vhdlLibraries.items():
value._parent = self
self._vhdlLibraries[key] = value
elif isinstance(vhdlLibraries, Iterable):
for item in vhdlLibraries:
item._parent = self
self._vhdlLibraries[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'libraries' is not an iterable of VHDLLibrary nor a mapping of VHDLLibrary.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibraries)}'.")
raise ex
self._testsuites = {}
if testsuites is None:
pass
elif isinstance(testsuites, Mapping):
for key, value in testsuites.items():
value._parent = self
self._testsuites[key] = value
elif isinstance(testsuites, Iterable):
for item in testsuites:
item._parent = self
self._testsuites[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'testsuites' is not an iterable of Testsuite nor a mapping of Testsuite.")
ex.add_note(f"Got type '{getFullyQualifiedName(testsuites)}'.")
raise ex
@readonly
def Project(self) -> Nullable["Project"]:
return self._parent
@readonly
def IncludedFiles(self) -> Generator[Path, None, None]:
return (file for file in self._includedFiles)
@readonly
def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
return self._vhdlLibraries
@readonly
def Testsuites(self) -> Dict[str, Testsuite]:
return self._testsuites
def AddVHDLLibrary(self, vhdlLibrary: VHDLLibrary) -> None:
if not isinstance(vhdlLibrary, VHDLLibrary): # pragma: no cover
ex = TypeError(f"Parameter 'vhdlLibrary' is not a VHDLLibrary.")
ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.")
raise ex
vhdlLibrary._parent = self
self._vhdlLibraries[vhdlLibrary._name] = vhdlLibrary
def AddTestsuite(self, testsuite: Testsuite) -> None:
if not isinstance(testsuite, Testsuite): # pragma: no cover
ex = TypeError(f"Parameter 'testsuite' is not a Testsuite.")
ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.")
raise ex
testsuite._parent = self
self._testsuites[testsuite._name] = testsuite
[docs]
def __repr__(self) -> str:
return f"Build: {self._name}"
[docs]
@export
class Project(Named[None]):
_builds: Dict[str, Build]
[docs]
def __init__(
self,
name: str,
builds: Nullable[Iterable[Build] | Mapping[str, Build]] = None
) -> None:
super().__init__(name, None)
self._builds = {}
if builds is None:
pass
elif isinstance(builds, Mapping):
for key, value in builds.items():
value._parent = self
self._builds[key] = value
elif isinstance(builds, Iterable):
for item in builds:
item._parent = self
self._builds[item._name] = item
else: # pragma: no cover
ex = TypeError(f"Parameter 'builds' is not an iterable of Build nor a mapping of Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(builds)}'.")
raise ex
@readonly
def Builds(self) -> Dict[str, Build]:
return self._builds
@readonly
def IncludedFiles(self) -> Generator[Path, None, None]:
for build in self._builds.values():
yield from build.IncludedFiles
def AddBuild(self, build: Build) -> None:
if not isinstance(build, Build): # pragma: no cover
ex = TypeError(f"Parameter 'build' is not a Build.")
ex.add_note(f"Got type '{getFullyQualifiedName(build)}'.")
raise ex
build._parent = self
self._builds[build._name] = build
[docs]
def __repr__(self) -> str:
return f"Project: {self._name}"
[docs]
@export
class Context(Base):
# _tcl: TclEnvironment
_processor: "OsvvmProFileProcessor"
_lastException: Exception
_workingDirectory: Path
_currentDirectory: Path
_includedFiles: List[Path]
_vhdlversion: VHDLVersion
_vhdlLibraries: Dict[str, VHDLLibrary]
_vhdlLibrary: Nullable[VHDLLibrary]
_testsuites: Dict[str, Testsuite]
_testsuite: Nullable[Testsuite]
_testcase: Nullable[Testcase]
_options: Dict[int, Option]
_builds: Dict[str, Build]
_build: Nullable[Build]
[docs]
def __init__(self) -> None:
super().__init__()
self._processor = None
self._lastException = None
self._workingDirectory = Path.cwd()
self._currentDirectory = self._workingDirectory
self._includedFiles = []
self._vhdlversion = VHDLVersion.VHDL2008
self._vhdlLibrary = None
self._vhdlLibraries = {}
self._testcase = None
self._testsuite = None
self._testsuites = {}
self._options = {}
self._build = None
self._builds = {}
def Clear(self) -> None:
self._processor = None
self._lastException = None
self._workingDirectory = Path.cwd()
self._currentDirectory = self._workingDirectory
self._includedFiles = []
self._vhdlversion = VHDLVersion.VHDL2008
self._vhdlLibrary = None
self._vhdlLibraries = {}
self._testcase = None
self._testsuite = None
self._testsuites = {}
self._options = {}
self._build = None
self._builds = {}
@readonly
def Processor(self): # -> "Tk":
return self._processor
@property
def LastException(self) -> Exception:
lastException = self._lastException
self._lastException = None
return lastException
@LastException.setter
def LastException(self, value: Exception) -> None:
self._lastException = value
@readonly
def WorkingDirectory(self) -> Path:
return self._workingDirectory
@readonly
def CurrentDirectory(self) -> Path:
return self._currentDirectory
@property
def VHDLVersion(self) -> VHDLVersion:
return self._vhdlversion
@VHDLVersion.setter
def VHDLVersion(self, value: VHDLVersion) -> None:
self._vhdlversion = value
@readonly
def IncludedFiles(self) -> List[Path]:
return self._includedFiles
@readonly
def VHDLLibraries(self) -> Dict[str, VHDLLibrary]:
return self._vhdlLibraries
@readonly
def VHDLLibrary(self) -> VHDLLibrary:
return self._vhdlLibrary
@readonly
def Testsuites(self) -> Dict[str, Testsuite]:
return self._testsuites
@readonly
def Testsuite(self) -> Testsuite:
return self._testsuite
@readonly
def TestCase(self) -> Testcase:
return self._testcase
@readonly
def Build(self) -> Build:
return self._build
@readonly
def Builds(self) -> Dict[str, Build]:
return self._builds
def ToProject(self, projectName: str) -> Project:
project = Project(projectName, self._builds)
return project
def BeginBuild(self, buildName: str) -> Build:
if len(self._vhdlLibraries) > 0:
raise OSVVMException(f"VHDL libraries have been created outside of an OSVVM build script.")
if len(self._testsuites) > 0:
raise OSVVMException(f"Testsuites have been created outside of an OSVVM build script.")
build = Build(buildName)
build._vhdlLibraries = self._vhdlLibraries
build._testsuites = self._testsuites
self._build = build
self._builds[buildName] = build
return build
def EndBuild(self) -> Build:
build = self._build
self._vhdlLibrary = None
self._vhdlLibraries = {}
self._testcase = None
self._testsuite = None
self._testsuites = {}
self._build = None
return build
def IncludeFile(self, proFileOrBuildDirectory: Path) -> Path:
if not isinstance(proFileOrBuildDirectory, Path): # pragma: no cover
ex = TypeError(f"Parameter 'proFileOrBuildDirectory' is not a Path.")
ex.add_note(f"Got type '{getFullyQualifiedName(proFileOrBuildDirectory)}'.")
self._lastException = ex
raise ex
if proFileOrBuildDirectory.is_absolute():
ex = OSVVMException(f"Absolute path '{proFileOrBuildDirectory}' not supported.")
self._lastException = ex
raise ex
path = (self._currentDirectory / proFileOrBuildDirectory).resolve()
if path.is_file():
if path.suffix == ".pro":
self._currentDirectory = path.parent.relative_to(self._workingDirectory, walk_up=True)
proFile = self._currentDirectory / path.name
else:
ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file.")
self._lastException = ex
raise ex
elif path.is_dir():
self._currentDirectory = path
proFile = path / "build.pro"
if not proFile.exists():
proFile = path / f"{path.name}.pro"
if not proFile.exists(): # pragma: no cover
ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a build directory.")
ex.__cause__ = FileNotFoundError(path / "build.pro")
self._lastException = ex
raise ex
else: # pragma: no cover
ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file or build directory.")
self._lastException = ex
raise ex
self._includedFiles.append(proFile)
return proFile
def EvaluateFile(self, proFile: Path) -> None:
self._processor.EvaluateProFile(proFile)
def SetLibrary(self, name: str):
try:
self._vhdlLibrary = self._vhdlLibraries[name]
except KeyError:
self._vhdlLibrary = VHDLLibrary(name, build=self._build)
self._vhdlLibraries[name] = self._vhdlLibrary
def AddVHDLFile(self, vhdlFile: VHDLSourceFile) -> None:
if self._vhdlLibrary is None:
self.SetLibrary("default")
vhdlFile.VHDLVersion = self._vhdlversion
self._vhdlLibrary.AddFile(vhdlFile)
def SetTestsuite(self, testsuiteName: str):
try:
self._testsuite = self._testsuites[testsuiteName]
except KeyError:
self._testsuite = Testsuite(testsuiteName)
self._testsuites[testsuiteName] = self._testsuite
def AddTestcase(self, testName: str) -> TestCase:
if self._testsuite is None:
self.SetTestsuite("default")
self._testcase = Testcase(testName)
self._testsuite._testcases[testName] = self._testcase
return self._testcase
def SetTestcaseToplevel(self, toplevel: str) -> TestCase:
if self._testcase is None:
ex = OSVVMException("Can't set testcase toplevel, because no testcase was setup.")
self._lastException = ex
raise ex
self._testcase.SetToplevel(toplevel)
return self._testcase
def AddOption(self, option: Option) -> int:
optionID = id(option)
self._options[optionID] = option
return optionID
osvvmContext: Context = Context()
"""
Global OSVVM processing context.
:type: Context
"""