We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Docs] Graph for Commands API have non working links
### The issue:

https://docs.qtile.org/en/latest/manual/commands/api/root.html

This is about the online documentation. I created an issue here, because its hosted in same repository as the code itself. The links on the graphical nodes are wrong and returns a 404. It's this image: https://docs.qtile.org/en/latest/_images/graphviz-52d4c4e0812a40a7c28aae165439b7e828011b3c.png

### Required:

- [x] I have searched past issues to see if this bug has already been reported.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `docs/qtile_docs/graph.py`
Content:
```
1 # Copyright (c) 2022 elParaguayo
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # of this software and associated documentation files (the "Software"), to deal
5 # in the Software without restriction, including without limitation the rights
6 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 # copies of the Software, and to permit persons to whom the Software is
8 # furnished to do so, subject to the following conditions:
9 #
10 # The above copyright notice and this permission notice shall be included in
11 # all copies or substantial portions of the Software.
12 #
13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 # SOFTWARE.
20 from dataclasses import dataclass
21 
22 from docutils.parsers.rst import Directive, directives
23 from qtile_docs.base import SimpleDirectiveMixin
24 from qtile_docs.templates import qtile_graph_template
25 
26 from libqtile.command import graph
27 
28 DISABLED_COLOUR = "Gray"
29 
30 
31 @dataclass
32 class Node:
33     node: graph.CommandGraphNode
34     x: float
35     y: float
36     fillcolor: str
37     color: str
38     url: str
39 
40     @property
41     def name(self):
42         return getattr(self.node, "object_type", "root")
43 
44     @property
45     def children(self):
46         return self.node.children
47 
48     def node_args(self, enabled=True, highlight=False):
49         """Returns a dict of arguments that can be formatted for graphviz."""
50         return {
51             "pos": f"{self.x},{self.y}!",
52             "color": self.color if enabled else DISABLED_COLOUR,
53             "fillcolor": self.fillcolor if enabled else DISABLED_COLOUR,
54             "href": self.url,
55             "style": "filled",
56             "label": self.name,
57             "fontname": "bold" if highlight else "regular",
58         }
59 
60 
61 ROOT = graph.CommandGraphRoot()
62 
63 
64 # Define our nodes with their positions, colours and link to API docs page.
65 NODES = [
66     Node(ROOT, 0, 0, "Gray", "DarkGray", "/manual/commands/api/root.html"),
67     Node(graph._BarGraphNode, -1.94, -0.44, "Violet", "Purple", "/manual/commands/api/bars.html"),
68     Node(
69         graph._CoreGraphNode,
70         -1.56,
71         1.24,
72         "SlateBlue1",
73         "SlateBlue",
74         "/manual/commands/api/backend.html",
75     ),
76     Node(
77         graph._GroupGraphNode,
78         1.56,
79         1.24,
80         "Orange",
81         "OrangeRed",
82         "/manual/commands/api/groups.html",
83     ),
84     Node(
85         graph._LayoutGraphNode,
86         1.94,
87         -0.44,
88         "Gold",
89         "Goldenrod",
90         "/manual/commands/api/layouts.html",
91     ),
92     Node(
93         graph._ScreenGraphNode,
94         0.86,
95         -1.8,
96         "LimeGreen",
97         "DarkGreen",
98         "/manual/commands/api/screens.html",
99     ),
100     Node(
101         graph._WidgetGraphNode,
102         -0.86,
103         -1.8,
104         "LightBlue",
105         "Blue",
106         "/manual/commands/api/widgets.html",
107     ),
108     Node(graph._WindowGraphNode, 0, 2, "Tomato", "Red", "/manual/commands/api/windows.html"),
109 ]
110 
111 
112 # Convenient dict to access node object via node name
113 NODES_MAP = {n.name: n for n in NODES}
114 
115 
116 COMMAND_MAP = {n.name: n.children for n in NODES}
117 
118 
119 # Generate a list of all routest in the map.
120 # Each route is a tuple of (start, end, bidirectional)
121 ROUTES = []
122 for node, children in COMMAND_MAP.items():
123     for child in children:
124         route = (node, child, node in COMMAND_MAP[child])
125         # Check that the reverse route is not in the list already
126         if (child, node, node in COMMAND_MAP[child]) not in ROUTES:
127             ROUTES.append(route)
128 
129 
130 class QtileGraph(SimpleDirectiveMixin, Directive):
131     required_arguments = 0
132     option_spec = {
133         "root": directives.unchanged,
134     }
135 
136     def make_nodes(self):
137         """Generates the node definition lines."""
138         node_lines = []
139 
140         for name, node in NODES_MAP.items():
141             args_dict = node.node_args(name in self.visible_nodes, name == self.graph_name)
142             args_string = ", ".join(f'{k}="{v}"' for k, v in args_dict.items())
143             node_lines.extend([f"node [{args_string}];", f"{name};", ""])
144 
145         return node_lines
146 
147     def make_routes(self):
148         """Generates the route definition lines."""
149         route_lines = []
150         for r in ROUTES:
151             args = {}
152             if r not in self.visible_routes:
153                 args["color"] = DISABLED_COLOUR
154             if r[2]:
155                 args["dir"] = "both"
156 
157             line = f"{r[0]} -> {r[1]}"
158             if args:
159                 args_string = ", ".join(f'{k}="{v}"' for k, v in args.items())
160                 line += f" [{args_string}]"
161             line += ";"
162             route_lines.append(line)
163 
164         return route_lines
165 
166     def find_linked_nodes_routes(self, node):
167         """Identifies routes connected to the selected node."""
168         nodes = []
169         routes = []
170         for r in ROUTES:
171             # Our node is the starting node
172             if r[0] == node:
173                 nodes.append(r[1])
174                 routes.append(r)
175             # Our node is the ending node and it's a bidirectional route
176             elif r[1] == node and r[2]:
177                 nodes.append(r[0])
178                 routes.append(r)
179 
180         return (nodes, routes)
181 
182     def make_rst(self):
183         self.graph_name = self.options.get("root", "all")
184         if self.graph_name == "all":
185             self.visible_nodes = [n for n in NODES_MAP]
186             self.visible_routes = ROUTES[:]
187         else:
188             linked_nodes, linked_routes = self.find_linked_nodes_routes(self.graph_name)
189             self.visible_nodes = [self.graph_name]
190             self.visible_nodes.extend(linked_nodes)
191             self.visible_routes = linked_routes
192 
193         graph = []
194         graph.append(f"strict digraph {self.graph_name} {{")
195         graph.append('bgcolor="transparent"')
196         graph.extend(self.make_nodes())
197         graph.extend(self.make_routes())
198         graph.append("}")
199 
200         rst = qtile_graph_template.render(graph=graph)
201         for line in rst.splitlines():
202             yield line
203
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/docs/qtile_docs/graph.py b/docs/qtile_docs/graph.py
--- a/docs/qtile_docs/graph.py
+++ b/docs/qtile_docs/graph.py
@@ -45,13 +45,13 @@
     def children(self):
         return self.node.children
 
-    def node_args(self, enabled=True, highlight=False):
+    def node_args(self, enabled=True, highlight=False, relative_url=str()):
         """Returns a dict of arguments that can be formatted for graphviz."""
         return {
             "pos": f"{self.x},{self.y}!",
             "color": self.color if enabled else DISABLED_COLOUR,
             "fillcolor": self.fillcolor if enabled else DISABLED_COLOUR,
-            "href": self.url,
+            "href": f"{relative_url}{self.url}",
             "style": "filled",
             "label": self.name,
             "fontname": "bold" if highlight else "regular",
@@ -63,15 +63,15 @@
 
 # Define our nodes with their positions, colours and link to API docs page.
 NODES = [
-    Node(ROOT, 0, 0, "Gray", "DarkGray", "/manual/commands/api/root.html"),
-    Node(graph._BarGraphNode, -1.94, -0.44, "Violet", "Purple", "/manual/commands/api/bars.html"),
+    Node(ROOT, 0, 0, "Gray", "DarkGray", "root.html"),
+    Node(graph._BarGraphNode, -1.94, -0.44, "Violet", "Purple", "bars.html"),
     Node(
         graph._CoreGraphNode,
         -1.56,
         1.24,
         "SlateBlue1",
         "SlateBlue",
-        "/manual/commands/api/backend.html",
+        "backend.html",
     ),
     Node(
         graph._GroupGraphNode,
@@ -79,7 +79,7 @@
         1.24,
         "Orange",
         "OrangeRed",
-        "/manual/commands/api/groups.html",
+        "groups.html",
     ),
     Node(
         graph._LayoutGraphNode,
@@ -87,7 +87,7 @@
         -0.44,
         "Gold",
         "Goldenrod",
-        "/manual/commands/api/layouts.html",
+        "layouts.html",
     ),
     Node(
         graph._ScreenGraphNode,
@@ -95,7 +95,7 @@
         -1.8,
         "LimeGreen",
         "DarkGreen",
-        "/manual/commands/api/screens.html",
+        "screens.html",
     ),
     Node(
         graph._WidgetGraphNode,
@@ -103,9 +103,9 @@
         -1.8,
         "LightBlue",
         "Blue",
-        "/manual/commands/api/widgets.html",
+        "widgets.html",
     ),
-    Node(graph._WindowGraphNode, 0, 2, "Tomato", "Red", "/manual/commands/api/windows.html"),
+    Node(graph._WindowGraphNode, 0, 2, "Tomato", "Red", "windows.html"),
 ]
 
 
@@ -131,6 +131,7 @@
     required_arguments = 0
     option_spec = {
         "root": directives.unchanged,
+        "api_page_root": directives.unchanged,
     }
 
     def make_nodes(self):
@@ -138,7 +139,11 @@
         node_lines = []
 
         for name, node in NODES_MAP.items():
-            args_dict = node.node_args(name in self.visible_nodes, name == self.graph_name)
+            args_dict = node.node_args(
+                name in self.visible_nodes,
+                name == self.graph_name,
+                self.options.get("api_page_root", ""),
+            )
             args_string = ", ".join(f'{k}="{v}"' for k, v in args_dict.items())
             node_lines.extend([f"node [{args_string}];", f"{name};", ""])

{"golden_diff": "diff --git a/docs/qtile_docs/graph.py b/docs/qtile_docs/graph.py\n--- a/docs/qtile_docs/graph.py\n+++ b/docs/qtile_docs/graph.py\n@@ -45,13 +45,13 @@\n     def children(self):\n         return self.node.children\n \n-    def node_args(self, enabled=True, highlight=False):\n+    def node_args(self, enabled=True, highlight=False, relative_url=str()):\n         \"\"\"Returns a dict of arguments that can be formatted for graphviz.\"\"\"\n         return {\n             \"pos\": f\"{self.x},{self.y}!\",\n             \"color\": self.color if enabled else DISABLED_COLOUR,\n             \"fillcolor\": self.fillcolor if enabled else DISABLED_COLOUR,\n-            \"href\": self.url,\n+            \"href\": f\"{relative_url}{self.url}\",\n             \"style\": \"filled\",\n             \"label\": self.name,\n             \"fontname\": \"bold\" if highlight else \"regular\",\n@@ -63,15 +63,15 @@\n \n # Define our nodes with their positions, colours and link to API docs page.\n NODES = [\n-    Node(ROOT, 0, 0, \"Gray\", \"DarkGray\", \"/manual/commands/api/root.html\"),\n-    Node(graph._BarGraphNode, -1.94, -0.44, \"Violet\", \"Purple\", \"/manual/commands/api/bars.html\"),\n+    Node(ROOT, 0, 0, \"Gray\", \"DarkGray\", \"root.html\"),\n+    Node(graph._BarGraphNode, -1.94, -0.44, \"Violet\", \"Purple\", \"bars.html\"),\n     Node(\n         graph._CoreGraphNode,\n         -1.56,\n         1.24,\n         \"SlateBlue1\",\n         \"SlateBlue\",\n-        \"/manual/commands/api/backend.html\",\n+        \"backend.html\",\n     ),\n     Node(\n         graph._GroupGraphNode,\n@@ -79,7 +79,7 @@\n         1.24,\n         \"Orange\",\n         \"OrangeRed\",\n-        \"/manual/commands/api/groups.html\",\n+        \"groups.html\",\n     ),\n     Node(\n         graph._LayoutGraphNode,\n@@ -87,7 +87,7 @@\n         -0.44,\n         \"Gold\",\n         \"Goldenrod\",\n-        \"/manual/commands/api/layouts.html\",\n+        \"layouts.html\",\n     ),\n     Node(\n         graph._ScreenGraphNode,\n@@ -95,7 +95,7 @@\n         -1.8,\n         \"LimeGreen\",\n         \"DarkGreen\",\n-        \"/manual/commands/api/screens.html\",\n+        \"screens.html\",\n     ),\n     Node(\n         graph._WidgetGraphNode,\n@@ -103,9 +103,9 @@\n         -1.8,\n         \"LightBlue\",\n         \"Blue\",\n-        \"/manual/commands/api/widgets.html\",\n+        \"widgets.html\",\n     ),\n-    Node(graph._WindowGraphNode, 0, 2, \"Tomato\", \"Red\", \"/manual/commands/api/windows.html\"),\n+    Node(graph._WindowGraphNode, 0, 2, \"Tomato\", \"Red\", \"windows.html\"),\n ]\n \n \n@@ -131,6 +131,7 @@\n     required_arguments = 0\n     option_spec = {\n         \"root\": directives.unchanged,\n+        \"api_page_root\": directives.unchanged,\n     }\n \n     def make_nodes(self):\n@@ -138,7 +139,11 @@\n         node_lines = []\n \n         for name, node in NODES_MAP.items():\n-            args_dict = node.node_args(name in self.visible_nodes, name == self.graph_name)\n+            args_dict = node.node_args(\n+                name in self.visible_nodes,\n+                name == self.graph_name,\n+                self.options.get(\"api_page_root\", \"\"),\n+            )\n             args_string = \", \".join(f'{k}=\"{v}\"' for k, v in args_dict.items())\n             node_lines.extend([f\"node [{args_string}];\", f\"{name};\", \"\"])\n", "issue": "[Docs] Graph for Commands API have non working links\n### The issue:\n\nhttps://docs.qtile.org/en/latest/manual/commands/api/root.html\r\n\r\nThis is about the online documentation. I created an issue here, because its hosted in same repository as the code itself. The links on the graphical nodes are wrong and returns a 404. It's this image: https://docs.qtile.org/en/latest/_images/graphviz-52d4c4e0812a40a7c28aae165439b7e828011b3c.png\n\n### Required:\n\n- [x] I have searched past issues to see if this bug has already been reported.\n", "before_files": [{"content": "# Copyright (c) 2022 elParaguayo\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom dataclasses import dataclass\n\nfrom docutils.parsers.rst import Directive, directives\nfrom qtile_docs.base import SimpleDirectiveMixin\nfrom qtile_docs.templates import qtile_graph_template\n\nfrom libqtile.command import graph\n\nDISABLED_COLOUR = \"Gray\"\n\n\n@dataclass\nclass Node:\n    node: graph.CommandGraphNode\n    x: float\n    y: float\n    fillcolor: str\n    color: str\n    url: str\n\n    @property\n    def name(self):\n        return getattr(self.node, \"object_type\", \"root\")\n\n    @property\n    def children(self):\n        return self.node.children\n\n    def node_args(self, enabled=True, highlight=False):\n        \"\"\"Returns a dict of arguments that can be formatted for graphviz.\"\"\"\n        return {\n            \"pos\": f\"{self.x},{self.y}!\",\n            \"color\": self.color if enabled else DISABLED_COLOUR,\n            \"fillcolor\": self.fillcolor if enabled else DISABLED_COLOUR,\n            \"href\": self.url,\n            \"style\": \"filled\",\n            \"label\": self.name,\n            \"fontname\": \"bold\" if highlight else \"regular\",\n        }\n\n\nROOT = graph.CommandGraphRoot()\n\n\n# Define our nodes with their positions, colours and link to API docs page.\nNODES = [\n    Node(ROOT, 0, 0, \"Gray\", \"DarkGray\", \"/manual/commands/api/root.html\"),\n    Node(graph._BarGraphNode, -1.94, -0.44, \"Violet\", \"Purple\", \"/manual/commands/api/bars.html\"),\n    Node(\n        graph._CoreGraphNode,\n        -1.56,\n        1.24,\n        \"SlateBlue1\",\n        \"SlateBlue\",\n        \"/manual/commands/api/backend.html\",\n    ),\n    Node(\n        graph._GroupGraphNode,\n        1.56,\n        1.24,\n        \"Orange\",\n        \"OrangeRed\",\n        \"/manual/commands/api/groups.html\",\n    ),\n    Node(\n        graph._LayoutGraphNode,\n        1.94,\n        -0.44,\n        \"Gold\",\n        \"Goldenrod\",\n        \"/manual/commands/api/layouts.html\",\n    ),\n    Node(\n        graph._ScreenGraphNode,\n        0.86,\n        -1.8,\n        \"LimeGreen\",\n        \"DarkGreen\",\n        \"/manual/commands/api/screens.html\",\n    ),\n    Node(\n        graph._WidgetGraphNode,\n        -0.86,\n        -1.8,\n        \"LightBlue\",\n        \"Blue\",\n        \"/manual/commands/api/widgets.html\",\n    ),\n    Node(graph._WindowGraphNode, 0, 2, \"Tomato\", \"Red\", \"/manual/commands/api/windows.html\"),\n]\n\n\n# Convenient dict to access node object via node name\nNODES_MAP = {n.name: n for n in NODES}\n\n\nCOMMAND_MAP = {n.name: n.children for n in NODES}\n\n\n# Generate a list of all routest in the map.\n# Each route is a tuple of (start, end, bidirectional)\nROUTES = []\nfor node, children in COMMAND_MAP.items():\n    for child in children:\n        route = (node, child, node in COMMAND_MAP[child])\n        # Check that the reverse route is not in the list already\n        if (child, node, node in COMMAND_MAP[child]) not in ROUTES:\n            ROUTES.append(route)\n\n\nclass QtileGraph(SimpleDirectiveMixin, Directive):\n    required_arguments = 0\n    option_spec = {\n        \"root\": directives.unchanged,\n    }\n\n    def make_nodes(self):\n        \"\"\"Generates the node definition lines.\"\"\"\n        node_lines = []\n\n        for name, node in NODES_MAP.items():\n            args_dict = node.node_args(name in self.visible_nodes, name == self.graph_name)\n            args_string = \", \".join(f'{k}=\"{v}\"' for k, v in args_dict.items())\n            node_lines.extend([f\"node [{args_string}];\", f\"{name};\", \"\"])\n\n        return node_lines\n\n    def make_routes(self):\n        \"\"\"Generates the route definition lines.\"\"\"\n        route_lines = []\n        for r in ROUTES:\n            args = {}\n            if r not in self.visible_routes:\n                args[\"color\"] = DISABLED_COLOUR\n            if r[2]:\n                args[\"dir\"] = \"both\"\n\n            line = f\"{r[0]} -> {r[1]}\"\n            if args:\n                args_string = \", \".join(f'{k}=\"{v}\"' for k, v in args.items())\n                line += f\" [{args_string}]\"\n            line += \";\"\n            route_lines.append(line)\n\n        return route_lines\n\n    def find_linked_nodes_routes(self, node):\n        \"\"\"Identifies routes connected to the selected node.\"\"\"\n        nodes = []\n        routes = []\n        for r in ROUTES:\n            # Our node is the starting node\n            if r[0] == node:\n                nodes.append(r[1])\n                routes.append(r)\n            # Our node is the ending node and it's a bidirectional route\n            elif r[1] == node and r[2]:\n                nodes.append(r[0])\n                routes.append(r)\n\n        return (nodes, routes)\n\n    def make_rst(self):\n        self.graph_name = self.options.get(\"root\", \"all\")\n        if self.graph_name == \"all\":\n            self.visible_nodes = [n for n in NODES_MAP]\n            self.visible_routes = ROUTES[:]\n        else:\n            linked_nodes, linked_routes = self.find_linked_nodes_routes(self.graph_name)\n            self.visible_nodes = [self.graph_name]\n            self.visible_nodes.extend(linked_nodes)\n            self.visible_routes = linked_routes\n\n        graph = []\n        graph.append(f\"strict digraph {self.graph_name} {{\")\n        graph.append('bgcolor=\"transparent\"')\n        graph.extend(self.make_nodes())\n        graph.extend(self.make_routes())\n        graph.append(\"}\")\n\n        rst = qtile_graph_template.render(graph=graph)\n        for line in rst.splitlines():\n            yield line\n", "path": "docs/qtile_docs/graph.py"}], "after_files": [{"content": "# Copyright (c) 2022 elParaguayo\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom dataclasses import dataclass\n\nfrom docutils.parsers.rst import Directive, directives\nfrom qtile_docs.base import SimpleDirectiveMixin\nfrom qtile_docs.templates import qtile_graph_template\n\nfrom libqtile.command import graph\n\nDISABLED_COLOUR = \"Gray\"\n\n\n@dataclass\nclass Node:\n    node: graph.CommandGraphNode\n    x: float\n    y: float\n    fillcolor: str\n    color: str\n    url: str\n\n    @property\n    def name(self):\n        return getattr(self.node, \"object_type\", \"root\")\n\n    @property\n    def children(self):\n        return self.node.children\n\n    def node_args(self, enabled=True, highlight=False, relative_url=str()):\n        \"\"\"Returns a dict of arguments that can be formatted for graphviz.\"\"\"\n        return {\n            \"pos\": f\"{self.x},{self.y}!\",\n            \"color\": self.color if enabled else DISABLED_COLOUR,\n            \"fillcolor\": self.fillcolor if enabled else DISABLED_COLOUR,\n            \"href\": f\"{relative_url}{self.url}\",\n            \"style\": \"filled\",\n            \"label\": self.name,\n            \"fontname\": \"bold\" if highlight else \"regular\",\n        }\n\n\nROOT = graph.CommandGraphRoot()\n\n\n# Define our nodes with their positions, colours and link to API docs page.\nNODES = [\n    Node(ROOT, 0, 0, \"Gray\", \"DarkGray\", \"root.html\"),\n    Node(graph._BarGraphNode, -1.94, -0.44, \"Violet\", \"Purple\", \"bars.html\"),\n    Node(\n        graph._CoreGraphNode,\n        -1.56,\n        1.24,\n        \"SlateBlue1\",\n        \"SlateBlue\",\n        \"backend.html\",\n    ),\n    Node(\n        graph._GroupGraphNode,\n        1.56,\n        1.24,\n        \"Orange\",\n        \"OrangeRed\",\n        \"groups.html\",\n    ),\n    Node(\n        graph._LayoutGraphNode,\n        1.94,\n        -0.44,\n        \"Gold\",\n        \"Goldenrod\",\n        \"layouts.html\",\n    ),\n    Node(\n        graph._ScreenGraphNode,\n        0.86,\n        -1.8,\n        \"LimeGreen\",\n        \"DarkGreen\",\n        \"screens.html\",\n    ),\n    Node(\n        graph._WidgetGraphNode,\n        -0.86,\n        -1.8,\n        \"LightBlue\",\n        \"Blue\",\n        \"widgets.html\",\n    ),\n    Node(graph._WindowGraphNode, 0, 2, \"Tomato\", \"Red\", \"windows.html\"),\n]\n\n\n# Convenient dict to access node object via node name\nNODES_MAP = {n.name: n for n in NODES}\n\n\nCOMMAND_MAP = {n.name: n.children for n in NODES}\n\n\n# Generate a list of all routest in the map.\n# Each route is a tuple of (start, end, bidirectional)\nROUTES = []\nfor node, children in COMMAND_MAP.items():\n    for child in children:\n        route = (node, child, node in COMMAND_MAP[child])\n        # Check that the reverse route is not in the list already\n        if (child, node, node in COMMAND_MAP[child]) not in ROUTES:\n            ROUTES.append(route)\n\n\nclass QtileGraph(SimpleDirectiveMixin, Directive):\n    required_arguments = 0\n    option_spec = {\n        \"root\": directives.unchanged,\n        \"api_page_root\": directives.unchanged,\n    }\n\n    def make_nodes(self):\n        \"\"\"Generates the node definition lines.\"\"\"\n        node_lines = []\n\n        for name, node in NODES_MAP.items():\n            args_dict = node.node_args(\n                name in self.visible_nodes,\n                name == self.graph_name,\n                self.options.get(\"api_page_root\", \"\"),\n            )\n            args_string = \", \".join(f'{k}=\"{v}\"' for k, v in args_dict.items())\n            node_lines.extend([f\"node [{args_string}];\", f\"{name};\", \"\"])\n\n        return node_lines\n\n    def make_routes(self):\n        \"\"\"Generates the route definition lines.\"\"\"\n        route_lines = []\n        for r in ROUTES:\n            args = {}\n            if r not in self.visible_routes:\n                args[\"color\"] = DISABLED_COLOUR\n            if r[2]:\n                args[\"dir\"] = \"both\"\n\n            line = f\"{r[0]} -> {r[1]}\"\n            if args:\n                args_string = \", \".join(f'{k}=\"{v}\"' for k, v in args.items())\n                line += f\" [{args_string}]\"\n            line += \";\"\n            route_lines.append(line)\n\n        return route_lines\n\n    def find_linked_nodes_routes(self, node):\n        \"\"\"Identifies routes connected to the selected node.\"\"\"\n        nodes = []\n        routes = []\n        for r in ROUTES:\n            # Our node is the starting node\n            if r[0] == node:\n                nodes.append(r[1])\n                routes.append(r)\n            # Our node is the ending node and it's a bidirectional route\n            elif r[1] == node and r[2]:\n                nodes.append(r[0])\n                routes.append(r)\n\n        return (nodes, routes)\n\n    def make_rst(self):\n        self.graph_name = self.options.get(\"root\", \"all\")\n        if self.graph_name == \"all\":\n            self.visible_nodes = [n for n in NODES_MAP]\n            self.visible_routes = ROUTES[:]\n        else:\n            linked_nodes, linked_routes = self.find_linked_nodes_routes(self.graph_name)\n            self.visible_nodes = [self.graph_name]\n            self.visible_nodes.extend(linked_nodes)\n            self.visible_routes = linked_routes\n\n        graph = []\n        graph.append(f\"strict digraph {self.graph_name} {{\")\n        graph.append('bgcolor=\"transparent\"')\n        graph.extend(self.make_nodes())\n        graph.extend(self.make_routes())\n        graph.append(\"}\")\n\n        rst = qtile_graph_template.render(graph=graph)\n        for line in rst.splitlines():\n            yield line\n", "path": "docs/qtile_docs/graph.py"}]}