gen_api_map : commentaire + généralisation

This commit is contained in:
Iziram 2024-06-21 15:38:44 +02:00
parent de47277e7c
commit 9b89ca436e
2 changed files with 244 additions and 53 deletions

View File

@ -724,7 +724,13 @@ def generate_ens_calendars(): # generate-ens-calendars
@app.cli.command() @app.cli.command()
@click.option(
"-e",
"--endpoint",
default="api",
help="Endpoint à partir duquel générer la carte des routes",
)
@with_appcontext @with_appcontext
def gen_api_map(): def gen_api_map(endpoint):
"""Show the API map""" """Génère la carte des routes de l'API."""
tools.gen_api_map(app) tools.gen_api_map(app, endpoint_start=endpoint)

View File

@ -1,15 +1,48 @@
"""
Script permettant de générer une carte SVG de l'API de ScoDoc
Écrit par Matthias HARTMANN
"""
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import re import re
class COLORS: class COLORS:
BLUE = "rgb(114,159,207)" """
GREEN = "rgb(165,214,165)" Couleurs utilisées pour les éléments de la carte
PINK = "rgb(230,156,190)" """
GREY = "rgb(224,224,224)"
BLUE = "rgb(114,159,207)" # Couleur de base / élément simple
GREEN = "rgb(165,214,165)" # Couleur route GET / valeur query
PINK = "rgb(230,156,190)" # Couleur route POST
GREY = "rgb(224,224,224)" # Couleur séparateur
class Token: class Token:
"""
Classe permettant de représenter un élément de l'API
Exemple :
/ScoDoc/api/test
Token(ScoDoc)-> Token(api) -> Token(test)
Chaque token peut avoir des enfants (Token) et des paramètres de query
Chaque token dispose
d'un nom (texte écrit dans le rectangle),
d'une méthode (GET ou POST) par défaut GET,
d'une func_name (nom de la fonction associée à ce token)
[OPTIONNEL] d'une query (dictionnaire {param: <type:nom_param>})
Un token est une leaf si il n'a pas d'enfants.
Une LEAF possède un `?` renvoyant vers la doc de la route
Il est possible de forcer un token à être une pseudo LEAF en mettant force_leaf=True
Une PSEUDO LEAF possède aussi un `?` renvoyant vers la doc de la route
tout en ayant des enfants.
"""
def __init__(self, name, method="GET", query=None, leaf=False): def __init__(self, name, method="GET", query=None, leaf=False):
self.children: list["Token"] = [] self.children: list["Token"] = []
self.name: str = name self.name: str = name
@ -19,24 +52,42 @@ class Token:
self.func_name = "" self.func_name = ""
def add_child(self, child): def add_child(self, child):
"""
Ajoute un enfant à ce token
"""
self.children.append(child) self.children.append(child)
def find_child(self, name): def find_child(self, name):
"""
Renvoie l'enfant portant le nom `name` ou None si aucun enfant ne correspond
"""
for child in self.children: for child in self.children:
if child.name == name: if child.name == name:
return child return child
return None return None
def __repr__(self, level=0): def __repr__(self, level=0):
"""
représentation textuelle simplifiée de l'arbre
(ne prend pas en compte les query, les méthodes, les func_name, ...)
"""
ret = "\t" * level + f"({self.name})\n" ret = "\t" * level + f"({self.name})\n"
for child in self.children: for child in self.children:
ret += child.__repr__(level + 1) ret += child.__repr__(level + 1)
return ret return ret
def is_leaf(self): def is_leaf(self):
"""
Renvoie True si le token est une leaf, False sinon
(i.e. s'il n'a pas d'enfants)
(force_leaf n'est pas pris en compte ici)
"""
return len(self.children) == 0 return len(self.children) == 0
def get_height(self, y_step): def get_height(self, y_step):
"""
Renvoie la hauteur de l'élément en prenant en compte la hauteur de ses enfants
"""
# Calculer la hauteur totale des enfants # Calculer la hauteur totale des enfants
children_height = sum(child.get_height(y_step) for child in self.children) children_height = sum(child.get_height(y_step) for child in self.children)
@ -51,14 +102,19 @@ class Token:
def to_svg_group( def to_svg_group(
self, self,
x_offset=0, x_offset: int = 0,
y_offset=0, y_offset: int = 0,
x_step=150, x_step: int = 150,
y_step=50, y_step: int = 50,
parent_coords=None, parent_coords: tuple[tuple[int, int], tuple[int, int]] = None,
parent_children_nb=0, parent_children_nb: int = 0,
): ):
group = ET.Element("g") """
Transforme un token en un groupe SVG
(récursif, appelle la fonction sur ses enfants)
"""
group = ET.Element("g") # groupe principal
color = COLORS.BLUE color = COLORS.BLUE
if self.is_leaf(): if self.is_leaf():
if self.method == "GET": if self.method == "GET":
@ -66,22 +122,27 @@ class Token:
elif self.method == "POST": elif self.method == "POST":
color = COLORS.PINK color = COLORS.PINK
# Création du rectangle avec le nom du token et placement sur la carte
element = _create_svg_element(self.name, color) element = _create_svg_element(self.name, color)
element.set("transform", f"translate({x_offset}, {y_offset})") element.set("transform", f"translate({x_offset}, {y_offset})")
group.append(element)
# On récupère les coordonnées de début et de fin de l'élément pour les flèches
current_start_coords, current_end_coords = _get_anchor_coords( current_start_coords, current_end_coords = _get_anchor_coords(
element, x_offset, y_offset element, x_offset, y_offset
) )
# Préparation du lien vers la doc de la route
href = "#" + self.func_name.replace("_", "-") href = "#" + self.func_name.replace("_", "-")
if self.query: if self.query:
href += "-query" href += "-query"
question_mark_group = _create_question_mark_group(current_end_coords, href) question_mark_group = _create_question_mark_group(current_end_coords, href)
group.append(element)
# Add an arrow from the parent element to the current element # Ajout de la flèche partant du parent jusqu'à l'élément courant
if parent_coords and parent_children_nb > 1: if parent_coords and parent_children_nb > 1:
arrow = _create_arrow(parent_coords, current_start_coords) arrow = _create_arrow(parent_coords, current_start_coords)
group.append(arrow) group.append(arrow)
# Ajout du `/` si le token n'est pas une leaf (ne prend pas en compte force_leaf)
if not self.is_leaf(): if not self.is_leaf():
slash_group = _create_svg_element("/", COLORS.GREY) slash_group = _create_svg_element("/", COLORS.GREY)
slash_group.set( slash_group.set(
@ -89,6 +150,7 @@ class Token:
f"translate({x_offset + _get_element_width(element)}, {y_offset})", f"translate({x_offset + _get_element_width(element)}, {y_offset})",
) )
group.append(slash_group) group.append(slash_group)
# Ajout du `?` si le token est une leaf et possède une query
if self.is_leaf() and self.query: if self.is_leaf() and self.query:
slash_group = _create_svg_element("?", COLORS.GREY) slash_group = _create_svg_element("?", COLORS.GREY)
slash_group.set( slash_group.set(
@ -96,17 +158,23 @@ class Token:
f"translate({x_offset + _get_element_width(element)}, {y_offset})", f"translate({x_offset + _get_element_width(element)}, {y_offset})",
) )
group.append(slash_group) group.append(slash_group)
# Actualisation des coordonnées de fin
current_end_coords = _get_anchor_coords(group, 0, 0)[1] current_end_coords = _get_anchor_coords(group, 0, 0)[1]
# Gestion des éléments de la query
# Pour chaque élément on va créer :
# (param) (=) (valeur) (&)
query_y_offset = y_offset query_y_offset = y_offset
query_sub_element = ET.Element("g") query_sub_element = ET.Element("g")
for key, value in self.query.items(): for key, value in self.query.items():
# Création d'un sous-groupe pour chaque élément de la query
sub_group = ET.Element("g") sub_group = ET.Element("g")
# <param>=<value> # On décale l'élément de la query vers la droite par rapport à l'élément parent
translate_x = x_offset + x_step translate_x = x_offset + x_step
# <param> # création élément (param)
param_el = _create_svg_element(key, COLORS.BLUE) param_el = _create_svg_element(key, COLORS.BLUE)
param_el.set( param_el.set(
"transform", "transform",
@ -114,15 +182,16 @@ class Token:
) )
sub_group.append(param_el) sub_group.append(param_el)
# add Arrow from "query" to element # Ajout d'une flèche partant de l'élément "query" vers le paramètre courant
coords = ( coords = (
current_end_coords, current_end_coords,
_get_anchor_coords(param_el, translate_x, query_y_offset)[0], _get_anchor_coords(param_el, translate_x, query_y_offset)[0],
) )
sub_group.append(_create_arrow(*coords)) sub_group.append(_create_arrow(*coords))
# = # création élément (=)
equal_el = _create_svg_element("=", COLORS.GREY) equal_el = _create_svg_element("=", COLORS.GREY)
# On met à jour le décalage en fonction de l'élément précédent
translate_x = x_offset + x_step + _get_element_width(param_el) translate_x = x_offset + x_step + _get_element_width(param_el)
equal_el.set( equal_el.set(
"transform", "transform",
@ -130,8 +199,9 @@ class Token:
) )
sub_group.append(equal_el) sub_group.append(equal_el)
# <value> # création élément (value)
value_el = _create_svg_element(value, COLORS.GREEN) value_el = _create_svg_element(value, COLORS.GREEN)
# On met à jour le décalage en fonction des éléments précédents
translate_x = ( translate_x = (
x_offset x_offset
+ x_step + x_step
@ -142,9 +212,13 @@ class Token:
f"translate({translate_x}, {query_y_offset})", f"translate({translate_x}, {query_y_offset})",
) )
sub_group.append(value_el) sub_group.append(value_el)
# Si il y a qu'un seul élément dans la query, on ne met pas de `&`
if len(self.query) == 1: if len(self.query) == 1:
continue continue
# création élément (&)
ampersand_group = _create_svg_element("&", "rgb(224,224,224)") ampersand_group = _create_svg_element("&", "rgb(224,224,224)")
# On met à jour le décalage en fonction des éléments précédents
translate_x = ( translate_x = (
x_offset x_offset
+ x_step + x_step
@ -156,17 +230,29 @@ class Token:
) )
sub_group.append(ampersand_group) sub_group.append(ampersand_group)
# On décale le prochain élément de la query vers le bas
query_y_offset += y_step * 1.33 query_y_offset += y_step * 1.33
# On ajoute le sous-groupe (param = value &) au groupe de la query
query_sub_element.append(sub_group) query_sub_element.append(sub_group)
# On ajoute le groupe de la query à l'élément principal
group.append(query_sub_element) group.append(query_sub_element)
# Gestion des enfants du Token
# On met à jour les décalages en fonction des éléments précédents
y_offset = query_y_offset y_offset = query_y_offset
current_y_offset = y_offset current_y_offset = y_offset
# Pour chaque enfant, on crée un groupe SVG de façon récursive
for child in self.children: for child in self.children:
# On décale l'enfant vers la droite par rapport à l'élément parent
# Si il n'y a qu'un enfant, alors on colle l'enfant à l'élément parent
rel_x_offset = x_offset + _get_group_width(group) rel_x_offset = x_offset + _get_group_width(group)
if len(self.children) > 1: if len(self.children) > 1:
rel_x_offset += x_step rel_x_offset += x_step
# On crée le groupe SVG de l'enfant
child_group = child.to_svg_group( child_group = child.to_svg_group(
rel_x_offset, rel_x_offset,
current_y_offset, current_y_offset,
@ -175,10 +261,12 @@ class Token:
parent_coords=current_end_coords, parent_coords=current_end_coords,
parent_children_nb=len(self.children), parent_children_nb=len(self.children),
) )
# On ajoute le groupe de l'enfant au groupe principal
group.append(child_group) group.append(child_group)
# On met à jour le décalage Y en fonction de la hauteur de l'enfant
current_y_offset += child.get_height(y_step) current_y_offset += child.get_height(y_step)
# add `?` circle a:href to element # Ajout du `?` si le token est une pseudo leaf ou une leaf
if self.force_leaf or self.is_leaf(): if self.force_leaf or self.is_leaf():
group.append(question_mark_group) group.append(question_mark_group)
@ -186,23 +274,32 @@ class Token:
def _create_svg_element(text, color="rgb(230,156,190)"): def _create_svg_element(text, color="rgb(230,156,190)"):
# Dimensions and styling """
Fonction générale pour créer un élément SVG simple
(rectangle avec du texte à l'intérieur)
text : texte à afficher dans l'élément
color : couleur de l'élément
"""
# Paramètres de style de l'élément
padding = 5 padding = 5
font_size = 16 font_size = 16
rect_height = 30 rect_height = 30
rect_x = 10 rect_x = 10
rect_y = 20 rect_y = 20
# Estimate the text width # Estimation de la largeur du texte
text_width = ( text_width = (
len(text) * font_size * 0.6 len(text) * font_size * 0.6
) # Estimate based on average character width ) # On suppose que la largeur d'un caractère est 0.6 * font_size
# Largeur du rectangle = Largeur du texte + padding à gauche et à droite
rect_width = text_width + padding * 2 rect_width = text_width + padding * 2
# Create the SVG group element # Création du groupe SVG
group = ET.Element("g") group = ET.Element("g")
# Create the rectangle # Création du rectangle
ET.SubElement( ET.SubElement(
group, group,
"rect", "rect",
@ -215,7 +312,7 @@ def _create_svg_element(text, color="rgb(230,156,190)"):
}, },
) )
# Create the text element # Création du texte
text_element = ET.SubElement( text_element = ET.SubElement(
group, group,
"text", "text",
@ -223,47 +320,66 @@ def _create_svg_element(text, color="rgb(230,156,190)"):
"x": str(rect_x + padding), "x": str(rect_x + padding),
"y": str( "y": str(
rect_y + rect_height / 2 + font_size / 2.5 rect_y + rect_height / 2 + font_size / 2.5
), # Adjust to vertically center the text ), # Ajustement pour centrer verticalement
"font-family": "Courier New, monospace", "font-family": "Courier New, monospace",
"font-size": str(font_size), "font-size": str(font_size),
"fill": "black", "fill": "black",
"style": "white-space: pre;", "style": "white-space: pre;",
}, },
) )
# Ajout du texte à l'élément
text_element.text = text text_element.text = text
return group return group
def _get_anchor_coords(element, x_offset, y_offset): def _get_anchor_coords(element, x_offset, y_offset):
"""
Récupération des coordonnées des points d'ancrage d'un élément SVG
(début et fin de l'élément pour les flèches)
(le milieu vertical de l'élément est utilisé pour les flèches)
"""
bbox = _get_bbox(element, x_offset, y_offset) bbox = _get_bbox(element, x_offset, y_offset)
startX = bbox["x_min"] startX = bbox["x_min"]
endX = bbox["x_max"] endX = bbox["x_max"]
# Milieu vertical de l'élément
y = bbox["y_min"] + (bbox["y_max"] - bbox["y_min"]) / 2 y = bbox["y_min"] + (bbox["y_max"] - bbox["y_min"]) / 2
return (startX, y), (endX, y) return (startX, y), (endX, y)
def _create_arrow(start_coords, end_coords): def _create_arrow(start_coords, end_coords):
"""
Création d'une flèche entre deux points
"""
# On récupère les coordonnées de début et de fin de la flèche
start_x, start_y = start_coords start_x, start_y = start_coords
end_x, end_y = end_coords end_x, end_y = end_coords
# On calcule le milieu horizontal de la flèche
mid_x = (start_x + end_x) / 2 mid_x = (start_x + end_x) / 2
# On crée le chemin de la flèche
path_data = ( path_data = (
f"M {start_x},{start_y} L {mid_x},{start_y} L {mid_x},{end_y} L {end_x},{end_y}" f"M {start_x},{start_y} L {mid_x},{start_y} L {mid_x},{end_y} L {end_x},{end_y}"
) )
# On crée l'élément path de la flèche
path = ET.Element( path = ET.Element(
"path", "path",
{ {
"d": path_data, "d": path_data,
"style": "stroke:black;stroke-width:2;fill:none", "style": "stroke:black;stroke-width:2;fill:none",
"marker-end": "url(#arrowhead)", "marker-end": "url(#arrowhead)", # Ajout de la flèche à la fin du path
}, },
) )
return path return path
def _get_element_width(element): def _get_element_width(element):
"""
Retourne la largueur d'un élément simple
L'élément simple correspond à un rectangle avec du texte à l'intérieur
on récupère la largueur du rectangle
"""
rect = element.find("rect") rect = element.find("rect")
if rect is not None: if rect is not None:
return float(rect.get("width", 0)) return float(rect.get("width", 0))
@ -271,18 +387,27 @@ def _get_element_width(element):
def _get_group_width(group): def _get_group_width(group):
"""
Récupère la largeur d'un groupe d'éléments
on fait la somme des largeurs de chaque élément du groupe
"""
return sum(_get_element_width(child) for child in group) return sum(_get_element_width(child) for child in group)
def _create_question_mark_group(coords, href): def _create_question_mark_group(coords, href):
"""
Création d'un groupe SVG contenant un cercle et un lien vers la doc de la route
le `?` renvoie vers la doc de la route
"""
# Récupération du point d'ancrage de l'élément
x, y = coords x, y = coords
radius = 10 # Radius of the circle radius = 10 # Rayon du cercle
y -= radius * 2 y -= radius * 2
font_size = 17 # Font size of the question mark font_size = 17 # Taille de la police
group = ET.Element("g") group = ET.Element("g")
# Create the circle # Création du cercle
ET.SubElement( ET.SubElement(
group, group,
"circle", "circle",
@ -296,17 +421,17 @@ def _create_question_mark_group(coords, href):
}, },
) )
# Create the link element # Création du lien (a) vers la doc de la route
link = ET.Element("a", {"href": href}) link = ET.Element("a", {"href": href})
# Create the text element # Création du texte `?`
text_element = ET.SubElement( text_element = ET.SubElement(
link, link,
"text", "text",
{ {
"x": str(x + 1), "x": str(x + 1),
"y": str(y + font_size / 3), # Adjust to vertically center the text "y": str(y + font_size / 3), # Ajustement pour centrer verticalement
"text-anchor": "middle", # Center the text horizontally "text-anchor": "middle", # Centrage horizontal
"font-family": "Arial", "font-family": "Arial",
"font-size": str(font_size), "font-size": str(font_size),
"fill": "black", "fill": "black",
@ -319,23 +444,49 @@ def _create_question_mark_group(coords, href):
return group return group
def gen_api_map(app): def gen_api_map(app, endpoint_start="api"):
"""
Fonction permettant de générer une carte SVG de l'API de ScoDoc
Elle récupère les routes de l'API et les transforme en un arbre de Token
puis génère un fichier SVG à partir de cet arbre
"""
# Création du token racine
api_map = Token("") api_map = Token("")
# Parcours de toutes les routes de l'application
for rule in app.url_map.iter_rules(): for rule in app.url_map.iter_rules():
# On ne garde que les routes de l'API / APIWEB # On ne garde que les routes de l'API / APIWEB
if not rule.endpoint.lower().startswith("api"): if not rule.endpoint.lower().startswith(endpoint_start.lower()):
continue continue
# Transformation de la route en segments
# ex : /ScoDoc/api/test -> ["ScoDoc", "api", "test"]
segments = rule.rule.strip("/").split("/") segments = rule.rule.strip("/").split("/")
# On positionne le token courant sur le token racine
current_token = api_map current_token = api_map
# Pour chaque segment de la route
for i, segment in enumerate(segments): for i, segment in enumerate(segments):
# Check if the segment already exists in the current level # On cherche si le segment est déjà un enfant du token courant
child = current_token.find_child(segment) child = current_token.find_child(segment)
# Si ce n'est pas le cas on crée un nouveau token et on l'ajoute comme enfant
if child is None: if child is None:
func = app.view_functions[rule.endpoint] func = app.view_functions[rule.endpoint]
# If it's the last segment # Si c'est le dernier segment, on marque le token comme une leaf
# On utilise force_leaf car il est possible que le token ne soit que
# momentanément une leaf
# ex :
# - /ScoDoc/api/test/ -> ["ScoDoc", "api", "test"]
# - /ScoDoc/api/test/1 -> ["ScoDoc", "api", "test", "1"]
# dans le premier cas test est une leaf, dans le deuxième cas test n'est pas une leaf
# force_leaf permet de forcer le token à être une leaf même s'il a des enfants
# permettant d'afficher le `?` renvoyant vers la doc de la route
# car la route peut être utilisée sans forcément la continuer.
if i == len(segments) - 1: if i == len(segments) - 1:
# Un Token sera query si parse_query_doc retourne un dictionnaire non vide
child = Token( child = Token(
segment, segment,
leaf=True, leaf=True,
@ -345,16 +496,18 @@ def gen_api_map(app):
child = Token( child = Token(
segment, segment,
) )
# On ajoute le token comme enfant du token courant
# en donnant la méthode et le nom de la fonction associée
child.func_name = func.__name__ child.func_name = func.__name__
method: str = "POST" if "POST" in rule.methods else "GET" method: str = "POST" if "POST" in rule.methods else "GET"
child.method = method child.method = method
current_token.add_child(child) current_token.add_child(child)
# On met à jour le token courant pour le prochain segment
current_token = child current_token = child
# Mark the last segment as a leaf node if it's not already marked # On génère le SVG à partir de l'arbre de Token
if not current_token.is_leaf():
current_token.force_leaf = True
generate_svg(api_map.to_svg_group(), "/tmp/api_map.svg") generate_svg(api_map.to_svg_group(), "/tmp/api_map.svg")
print( print(
"La carte a été générée avec succès. " "La carte a été générée avec succès. "
@ -363,7 +516,12 @@ def gen_api_map(app):
def _get_bbox(element, x_offset=0, y_offset=0): def _get_bbox(element, x_offset=0, y_offset=0):
# Helper function to calculate the bounding box of an SVG element """
Récupérer les coordonnées de la boîte englobante d'un élément SVG
Utilisé pour calculer les coordonnées d'un élément SVG et pour avoir la taille
total du SVG
"""
# Initialisation des coordonnées de la boîte englobante
bbox = { bbox = {
"x_min": float("inf"), "x_min": float("inf"),
"y_min": float("inf"), "y_min": float("inf"),
@ -371,17 +529,26 @@ def _get_bbox(element, x_offset=0, y_offset=0):
"y_max": float("-inf"), "y_max": float("-inf"),
} }
# Parcours récursif des enfants de l'élément
for child in element: for child in element:
# On récupère la transformation (position) de l'enfant
# On met les OffSet par défaut à leur valeur donnée en paramètre
transform = child.get("transform") transform = child.get("transform")
child_x_offset = x_offset child_x_offset = x_offset
child_y_offset = y_offset child_y_offset = y_offset
# Si la transformation est définie, on récupère les coordonnées de translation
# et on les ajoute aux offsets
if transform: if transform:
translate = transform.replace("translate(", "").replace(")", "").split(",") translate = transform.replace("translate(", "").replace(")", "").split(",")
if len(translate) == 2: if len(translate) == 2:
child_x_offset += float(translate[0]) child_x_offset += float(translate[0])
child_y_offset += float(translate[1]) child_y_offset += float(translate[1])
# On regarde ensuite la boite englobante de l'enfant
# On met à jour les coordonnées de la boîte englobante en fonction de l'enfant
# x_min, y_min, x_max, y_max.
if child.tag == "rect": if child.tag == "rect":
x = child_x_offset + float(child.get("x", 0)) x = child_x_offset + float(child.get("x", 0))
y = child_y_offset + float(child.get("y", 0)) y = child_y_offset + float(child.get("y", 0))
@ -403,10 +570,16 @@ def _get_bbox(element, x_offset=0, y_offset=0):
def generate_svg(element, fname): def generate_svg(element, fname):
"""
Génère un fichier SVG à partir d'un élément SVG
"""
# On récupère les dimensions de l'élément racine
bbox = _get_bbox(element) bbox = _get_bbox(element)
width = bbox["x_max"] - bbox["x_min"] + 80 # Add some padding # On définit la taille du SVG en fonction des dimensions de l'élément racine
height = bbox["y_max"] - bbox["y_min"] + 80 # Add some padding width = bbox["x_max"] - bbox["x_min"] + 80
height = bbox["y_max"] - bbox["y_min"] + 80
# Création de l'élément racine du SVG
svg = ET.Element( svg = ET.Element(
"svg", "svg",
{ {
@ -417,7 +590,8 @@ def generate_svg(element, fname):
}, },
) )
# Define the marker for the arrowhead # Création du motif de la flèche pour les liens
# (définition d'un marqueur pour les flèches)
defs = ET.SubElement(svg, "defs") defs = ET.SubElement(svg, "defs")
marker = ET.SubElement( marker = ET.SubElement(
defs, defs,
@ -433,8 +607,10 @@ def generate_svg(element, fname):
) )
ET.SubElement(marker, "polygon", {"points": "0 0, 10 3.5, 0 7"}) ET.SubElement(marker, "polygon", {"points": "0 0, 10 3.5, 0 7"})
# Ajout de l'élément principal à l'élément racine
svg.append(element) svg.append(element)
# Écriture du fichier SVG
tree = ET.ElementTree(svg) tree = ET.ElementTree(svg)
tree.write(fname, encoding="utf-8", xml_declaration=True) tree.write(fname, encoding="utf-8", xml_declaration=True)
@ -454,30 +630,39 @@ def parse_query_doc(doc):
Dès qu'une ligne ne respecte pas ce format (voir regex dans la fonction), on arrête de parser Dès qu'une ligne ne respecte pas ce format (voir regex dans la fonction), on arrête de parser
Attention, la ligne ----- doit être collée contre QUERY et contre le premier paramètre Attention, la ligne ----- doit être collée contre QUERY et contre le premier paramètre
""" """
# Récupérer les lignes de la doc
lines = [line.strip() for line in doc.split("\n")] lines = [line.strip() for line in doc.split("\n")]
# On cherche la ligne "QUERY" et on vérifie que la ligne suivante est "-----"
# Si ce n'est pas le cas, on renvoie un dictionnaire vide
try: try:
query_index = lines.index("QUERY") query_index = lines.index("QUERY")
if lines[query_index + 1] != "-----": if lines[query_index + 1] != "-----":
return {} return {}
except ValueError: except ValueError:
return {} return {}
# On récupère les lignes de la doc qui correspondent à la query (enfin on espère)
query_lines = lines[query_index + 2 :] query_lines = lines[query_index + 2 :]
query = {} query = {}
regex = re.compile(r"^(\w+):(<.+>)$") regex = re.compile(r"^(\w+):(<.+>)$")
for line in query_lines: for line in query_lines:
# On verifie que la ligne respecte le format attendu
# Si ce n'est pas le cas, on arrête de parser
parts = regex.match(line) parts = regex.match(line)
if not parts: if not parts:
break break
# On récupère le paramètre et son type:nom
param, type_nom_param = parts.groups() param, type_nom_param = parts.groups()
# On ajoute le paramètre au dictionnaire
query[param] = type_nom_param query[param] = type_nom_param
return query return query
if __name__ == "__main__": if __name__ == "__main__":
# Exemple d'utilisation de la classe Token
# Exemple simple de création d'un arbre de Token
root = Token("api") root = Token("api")
child1 = Token("assiduites", leaf=True) child1 = Token("assiduites", leaf=True)
child1.func_name = "assiduites_get" child1.func_name = "assiduites_get"