"""App module.
This module define all the class related to UI.
"""
import os
import secrets
import threading
from typing import Any, Dict, List, Tuple
from flask import Flask, render_template
from flask_classful import FlaskView, route
from flask_socketio import SocketIO
from flaskwebgui import FlaskUI
from injector import inject, singleton
[docs]class UiLink:
"""Base of UI communation using web sockets"""
def __init__(self) -> None:
self.socketio: SocketIO = None
[docs] def setSocketIO(self, socketio: SocketIO) -> None:
self.socketio = socketio
[docs]@singleton
class UiFileDispatcherListener(UiLink):
[docs] def notifyAnalyzing(self, file: str) -> None:
if self.socketio:
self.socketio.emit("fileAnalyzedChange", {"file": file})
[docs] def notifyAnalysisEnd(self) -> None:
if self.socketio:
self.socketio.emit("analysisCompleted", {})
[docs]@singleton
class UiStatListener(UiLink):
[docs] def notifyStats(
self,
nbFiles: int,
nbClasses: int,
nbEnums: int,
nbFunctions: int,
languagePie: str,
duration: float,
) -> None:
if self.socketio:
self.socketio.emit(
"statsChange",
{
"nbFiles": nbFiles,
"nbClasses": nbClasses,
"nbEnums": nbEnums,
"nbFunctions": nbFunctions,
"languagePie": languagePie,
"duration": duration,
},
)
[docs]@singleton
class UiBrowseListener(UiLink):
[docs] def notifyClasseNames(self, tree: Dict[str, Any]) -> None:
if self.socketio:
self.socketio.emit("classeNamesChange", {"tree": tree})
[docs] def notifyEnumNames(self, tree: Dict[str, Any]) -> None:
if self.socketio:
self.socketio.emit("enumNamesChange", {"tree": tree})
[docs] def notifyFunctionNames(self, tree: Dict[str, Any]) -> None:
if self.socketio:
self.socketio.emit("functionNamesChange", {"tree": tree})
[docs] def notifyFileNames(self, tree: Dict[str, Any]) -> None:
if self.socketio:
self.socketio.emit("fileNamesChange", {"tree": tree})
[docs] def notifyClassData(self, klass: Dict[str, Any], mermaidDiag: str) -> None:
if self.socketio:
self.socketio.emit(
"classDataChange", {"class": klass, "mermaidDiag": mermaidDiag}
)
[docs] def notifyEnumData(self, enum: Dict[str, Any], mermaidDiag: str) -> None:
if self.socketio:
self.socketio.emit(
"enumDataChange", {"enum": enum, "mermaidDiag": mermaidDiag}
)
[docs] def notifyFunctionData(self, function: Dict[str, Any]) -> None:
if self.socketio:
self.socketio.emit("functionDataChange", {"function": function})
[docs] def notifyFileData(self, file: Dict[str, Any]) -> None:
if self.socketio:
self.socketio.emit("fileDataChange", {"file": file})
[docs] def notifySearchResult(self, searchRes: List[Tuple[str, str]]) -> None:
if self.socketio:
self.socketio.emit("searchResult", {"res": searchRes})
[docs] def notifyUsedByUse(self, activated: bool) -> None:
if self.socketio:
self.socketio.emit("usedByUseChange", {"activated": activated})
[docs]@singleton
class Application:
@inject
def __init__(
self,
uiFileDispatcherListener: UiFileDispatcherListener,
uiStatListener: UiStatListener,
uiBrowseListener: UiBrowseListener,
) -> None:
templateDir = os.path.join(
os.path.abspath(os.path.dirname(__file__)), "web/templates"
)
staticDir = os.path.join(
os.path.abspath(os.path.dirname(__file__)), "web/static"
)
self.app = Flask(
"pycodeanalyzer", template_folder=templateDir, static_folder=staticDir
)
self.app.config["SECRET_KEY"] = secrets.token_urlsafe(16)
self.app.use_reloader = True
self.socketio = SocketIO(self.app, logger=False, engineio_logger=False)
self.ui: FlaskUI = None
uiFileDispatcherListener.setSocketIO(self.socketio)
uiStatListener.setSocketIO(self.socketio)
uiStatListener.setSocketIO(self.socketio)
uiBrowseListener.setSocketIO(self.socketio)
FlaskHolder.register(self.app, route_base="/")
[docs] def run(self) -> None:
self.ui = FlaskUI(
self.app, socketio=self.socketio, start_server="flask-socketio"
)
threading.Thread(target=lambda: self.ui.run()).start()
[docs] def quit(self) -> None:
if self.ui:
self.ui.idle_interval = 0
[docs]class FlaskHolder(FlaskView):
[docs] @route("/")
def loading(self) -> str:
return render_template("loading.html")
[docs] @route("/home")
def home(self) -> str:
return render_template("home.html")
[docs] @route("/browse")
@route("/browse/classes")
@route("/browse/enums")
@route("/browse/functions")
@route("/browse/files")
@route("/browse/search")
def browse(self) -> str:
return render_template("browse.html")