import os import re from typing import Literal from pydantic import BaseModel from lib.phply.phpast import Class, MethodCall, Constant, UnaryOp from lib.phply.phplex import lexer from lib.phply.phpparse import make_parser HttpMethod = Literal["GET", "POST"] ControllerType = Literal["Abstract [non-callable]", "Service", "Resources"] class Action(BaseModel): command: str parameters: str methods: list[HttpMethod] model_path: str | None = None class Controller(BaseModel): actions: list[Action] type: ControllerType module: str controller: str is_abstract: bool base_class: str | None filename: str model_filename: str | None model_container: str | None DEFAULT_BASE_METHODS = { "ApiMutableModelControllerBase": [Action( command="set", parameters="", methods=["POST"], ), Action( command="get", parameters="", methods=["GET"], )], "ApiMutableServiceControllerBase": [Action( command="status", parameters="", methods=["GET"], ), Action( command="start", parameters="", methods=["POST"], ), Action( command="stop", parameters="", methods=["POST"], ), Action( command="restart", parameters="", methods=["POST"], ), Action( command="reconfigure", parameters="", methods=["POST"], )] } class ApiParser: def __init__(self,filename: str,debug: bool=False): self._debug = debug self._filename = filename self.base_filename = os.path.basename(filename) self.controller = re.sub('(? Controller: """ collect api endpoints """ self.api_commands = {} self.parser = make_parser() self.parser.errorfunc = self._p_error for root in self.parser.parse(self._data, lexer=lexer.clone(), tracking=True): if type(root) is Class: self.is_abstract = root.type == 'abstract' self.base_class = root.extends for node in root.nodes: tmp = "".join("_" + c.lower() if c.isupper() else c for c in type(node).__name__) node_method = '_parse%s' % tmp if hasattr(self, node_method): getattr(self, node_method)(node) # stick base class defaults to the list when not yet defined if self.base_class in DEFAULT_BASE_METHODS: for item in DEFAULT_BASE_METHODS[self.base_class]: if item.command not in self.api_commands: self.api_commands[item.command] = item if self.is_abstract: controller_type = 'Abstract [non-callable]' elif self.controller.find('service') > -1: controller_type = 'Service' else: controller_type = 'Resources' return Controller( actions=sorted(self.api_commands.values(), key=lambda i: i.command), type=controller_type, module=self.module_name, controller=self.controller, is_abstract=self.is_abstract, base_class=self.base_class, filename=self.base_filename, model_filename=self.model_filename, model_container=self.model_container, )