lisa/docs/tools/doc_generator.py

150 строки
4.5 KiB
Python

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import ast
from pathlib import Path
from typing import Any, Dict, List, Set
import yaml
TESTS = Path("./test_paths.yaml")
EXTS = Path("./api_paths.yaml")
# TODO - API
class DocGenerator:
def __init__(self, filename: Path) -> None:
assert str(filename)[-3:] == ".py"
self._filename = filename
class ClassVisitor(ast.NodeVisitor):
def __init__(self) -> None:
self._class_names: Set[Any] = set()
self._suites: Set[Any] = set()
def visit_ClassDef(self, node: ast.ClassDef) -> None: # noqa
"""
Overrides parent method in ast.NodeVisitor, traverses all classes
"""
decorators = node.decorator_list
for deco in decorators:
if isinstance(deco, ast.Call) and isinstance(deco.func, ast.Name):
if deco.func.id.endswith("TestSuiteMetadata"):
self._suites.add(node)
if isinstance(node, ast.ClassDef):
self._class_names.add(node.name)
def get_suites(self) -> Set[Any]:
return self._suites
def get_class_names(self) -> Set[Any]:
return self._class_names
class FuncVisitor(ast.NodeVisitor):
def __init__(self) -> None:
self._cases: Set[Any] = set()
self._names: Set[Any] = set()
self._constants: Set[Any] = set()
def get_cases(self) -> Set[Any]:
return self._cases
def visit_FunctionDef(self, node: ast.FunctionDef) -> None: # noqa
"""
Overrides parent method in ast.NodeVisitor, traverses all functions
"""
decorators = node.decorator_list
for deco in decorators:
if isinstance(deco, ast.Call) and isinstance(deco.func, ast.Name):
if deco.func.id.endswith("TestCaseMetadata"):
self._cases.add(node)
def add_req(s: str, req: str) -> str:
"""
A helper function to format test requirement.
Args:
s (str): prefix, particularly, "supported_features"
req (str): requirement to add
Returns:
str: complete formatted requirement
"""
if "[" not in s:
s = s + "[" + req + "]"
else:
s = s[:-1] + ", " + req + "]"
return s
def extract_metadata(nodes: Set[Any]) -> List[Dict[str, str]]:
"""
Main function to extract and format metadata
Args:
nodes (Set[Any]): nodes containing metadata
Returns:
List[Dict[str, str]]: formatted metadata
"""
all_metadata = []
for node in nodes:
metadata: Dict[str, str] = {"name": node.name}
for deco in node.decorator_list:
for param in deco.keywords:
if isinstance(param.value, ast.Call): # requirement
for req in param.value.keywords:
val = req.arg
if isinstance(req.value, ast.Attribute): # may be wrong
val = (
f"{req.value.value.id}.{req.value.attr}" # type: ignore
)
elif isinstance(req.value, ast.Constant):
val = f"{req.arg}={req.value.value}"
elif isinstance(req.value, ast.List):
for r in req.value.elts:
if isinstance(r, ast.Name):
val = add_req(val, r.id) # type: ignore
elif isinstance(param.value, ast.Constant):
val = param.value.value
elif isinstance(param.value, ast.Name):
val = param.value.id
elif isinstance(param.value, ast.Attribute):
val = f"{param.value.value.id}.{param.value.attr}" # type: ignore
else:
raise ValueError(
f"param.value is unsupported type '{type(param.value)}'"
)
metadata[param.arg] = val # type: ignore
all_metadata.append(metadata)
return all_metadata
def load_path(file_path: Path) -> List[Dict[str, str]]:
"""
load file paths from a user-friendly yaml file
Args:
file_path (Path): path to the yaml file
Returns:
Dict[str, str]: usually name as key and path as value
"""
base_path = Path(__file__).parent
path = (base_path / file_path).resolve()
with open(path, "r") as file:
data: List[Dict[str, str]] = yaml.safe_load(file)
return data