-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmysql-workbench-plugin-doc-generating.py
272 lines (248 loc) · 8.53 KB
/
mysql-workbench-plugin-doc-generating.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# -*- coding: utf-8 -*-
# MySQL Workbench Plugin
# Written in MySQL Workbench 8.0.36
#
# Original Author: Hieu Le
# Author: Yao Lei <[email protected]>
# Xue Can <[email protected]>
import time
import traceback
import grt
import mforms
from wb import *
G = {
"LAST_CHANGE_DATE": [], # tables last change time; type: int(timestamp)
"DEFAULT_DATABASE": None,
"STATUS_TEXT": "",
}
ModuleInfo = DefineModule(
"ModelDocumentation",
author="NETPAS Developers",
version="1.2.0",
description="Generate Markdown documentation from a model",
)
# This plugin takes no arguments
@ModuleInfo.plugin(
"Netpas",
caption="Generate documentation (Markdown)",
description="description",
input=[wbinputs.currentDiagram()],
pluginMenu="Utilities",
)
@ModuleInfo.export(grt.INT, grt.classes.db_Catalog)
def documentation(diagram):
output("Generating documentation...", reset=True)
try:
G["DEFAULT_DATABASE"] = [
figure for figure in diagram.figures if hasattr(figure, "table")
][0].table.owner
db_obj = G["DEFAULT_DATABASE"]
# db name
title_text = "# {}\n\n".format(db_obj.name)
table_text = ""
view_text = ""
for figure in diagram.figures:
if hasattr(figure, "table") and figure.table:
table_text += writeTableDoc(figure.table)
if hasattr(figure, "view") and figure.view:
view_text += writeViewDoc(figure.view)
if view_text:
view_text = "# *Views*\n\n" + view_text
# db comment
title_text += (
"*{}*\n\n".format(nl2br(db_obj.comment)) if db_obj.comment else "\n\n"
)
# db last change date
last_change_time = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(max(G["LAST_CHANGE_DATE"]))
)
title_text += "{} *{}*\n\n".format(
"Automatically generate documents. The latest form of document changes by",
last_change_time,
)
# Database Structure
title_text += "\n\n".format(db_obj.name)
# merge text
text = title_text + table_text + view_text
set_clipboard_text(text)
output(
"Documentation generated into the clipboard. Paste it to your editor.",
reset=True,
)
print("Documentation is copied to the clipboard.")
except Exception as exc:
output(f"Oops! {exc}. See the clipboard for more information.", reset=True)
details = traceback.format_exc()
set_clipboard_text(details)
return 0
def writeTableDoc(table):
# table last change date
last_change_date = time.mktime(
time.strptime(table.owner.lastChangeDate, "%Y-%m-%d %H:%M")
)
G["LAST_CHANGE_DATE"].append(int(last_change_date))
text = ""
if G["DEFAULT_DATABASE"].name == table.owner.name:
text = "## **<a id='{}'></a>{}**\n\n".format(
table.name.lower().replace("_", "-"), table.name.lower()
)
text += "---\n\n"
text += "### *Description:*\n\n"
text += table.comment + "\n\n"
text += "### *Columns:*\n\n"
text += "| Column | Data type | Attributes | Default | Description |\n| --- | --- | --- | --- | --- |\n"
for column in table.columns:
text += writeColumnDoc(column, table)
text += "\n\n"
if len(table.indices):
text += "### *Indices:*\n\n"
text += (
"| Name | Columns | Type | Description |\n| --- | --- | --- | --- |\n"
)
for index in table.indices:
text += writeIndexDoc(index)
text += "\n\n"
return text
def writeColumnDoc(column, table):
# column attributes
attribs = []
isPrimary = False
isUnique = False
for index in table.indices:
if index.indexType == "PRIMARY":
for c in index.columns:
if c.referencedColumn.name == column.name:
isPrimary = True
break
if index.indexType == "UNIQUE":
for c in index.columns:
if c.referencedColumn.name == column.name:
isUnique = True
break
# primary?
if isPrimary:
# format label a
table_name = table.name.lower().replace("_", "-")
column_name = table.columns[0].name.lower().replace("_", "-")
# column name
text = "| <a id='{}-{}'></a>`{}`".format(table_name, column_name, column.name)
else:
# column name
text = "| `{}`".format(column.name)
# column type name
if column.simpleType:
text += " | " + column.simpleType.name
if "UNSIGNED" in column.flags:
text += " " + "UNSIGNED"
# column max lenght if any
if column.length != -1:
text += "(" + str(column.length) + ")"
if column.collationName.endswith("_bin"):
text += " " + "BINARY"
else:
text += " | "
text += " | "
# primary?
if isPrimary:
attribs.append("PRIMARY")
# auto increment?
if column.autoIncrement == 1:
attribs.append("Auto increments")
# not null?
if column.isNotNull == 1:
attribs.append("Not null")
# unique?
if isUnique:
attribs.append("Unique")
text += ", ".join(attribs)
# column default value
text += " | " + (
("`" + column.defaultValue.replace("'", "") + "`")
if column.defaultValue
else " "
)
# column description
text += " | " + (nl2br(column.comment) if column.comment else " ")
if "ENUM" in column.formattedType:
# text+=str(column.formattedType[4:])
text += "( "
values = column.formattedType[4:][2:-2]
values = values.replace("','", "`, `")
values = "`" + values + "`"
text += values
text += " )"
# value.replace()
if "SET" in column.formattedType:
text += "( "
values = column.formattedType[3:][2:-2]
values = values.replace("','", "`, `")
values = "`" + values + "`"
text += values
text += " )"
# foreign key
for fk in table.foreignKeys:
if len(fk.columns) == 0:
raise RuntimeError(f"Foreign key {fk.name} has no columns")
if fk.columns[0].name == column.name:
# redirect label a
fk_filed = fk.referencedColumns[0].name.lower()
rep_fk_filed = fk.referencedColumns[0].name.lower().replace("_", "-")
fk_table_name = fk.referencedColumns[0].owner.name.lower()
rep_fk_table_name = (
fk.referencedColumns[0].owner.name.lower().replace("_", "-")
)
text += (
("<br /><br />" if column.comment else "")
+ "REFERENCES"
+ " "
+ "[**{}**](#{}) ".format(fk_table_name, rep_fk_table_name)
+ "("
+ "[**{}**](#{}-{})".format(fk_filed, fk_table_name, rep_fk_filed)
+ ")"
)
if fk.updateRule != "RESTRICT":
text += " " + "ON UPDATE" + " " + fk.updateRule
if fk.deleteRule != "RESTRICT":
text += " " + "ON DELETE" + " " + fk.deleteRule
break
# finish
text += " |" + "\n"
return text
def writeIndexDoc(index):
# index name
text = "| " + index.name
# index columns
text += " | " + ", ".join(
map(lambda x: "`" + x.referencedColumn.name + "`", index.columns)
)
# index type
text += " | " + index.indexType
# index description
text += " | " + (nl2br(index.comment) if index.comment else " ")
# finish
text += " |\n"
return text
def writeViewDoc(view):
text = ""
if G["DEFAULT_DATABASE"].name == view.owner.name:
text = "## **<a id='{}'></a>{}**\n\n".format(
view.name.lower().replace("_", "-"), view.name.lower()
)
text += "---\n\n"
text += "### *Description:*\n\n"
text += view.comment + "\n\n"
text += "### *Sql:*\n\n"
text += "```sql" + "\n" + view.sqlDefinition + "\n" + "```" + "\n"
return text
def nl2br(text):
return "<br />".join(map(lambda x: x.strip(), text.split("\n")))
def set_clipboard_text(text):
mforms.Utilities.set_clipboard_text(text)
def set_status_text(text):
mforms.App.get().set_status_text(text)
def output(text, reset=False):
if reset:
G["STATUS_TEXT"] = text
else:
G["STATUS_TEXT"] = text + " | " + G["STATUS_TEXT"]
set_status_text(G["STATUS_TEXT"])