Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/dev-docs/feature-format-matrix/create_table.py
3557 views
1
import yaml
2
import json
3
import pathlib
4
5
6
class Trie:
7
def __init__(self):
8
self.children = {}
9
self.values = []
10
self.entry = ""
11
12
def insert(self, path, value):
13
node = self
14
for c in path:
15
if c not in node.children:
16
node.children[c] = Trie()
17
node = node.children[c]
18
node.values.append(value)
19
node.entry = value["entry"]
20
21
def json(self):
22
if not self.children:
23
return self.values
24
return {k: v.json() for k, v in self.children.items()}
25
26
def tabulator_leaf(self):
27
result = {}
28
for v in self.values:
29
result[v["format"]] = v["table_cell"]
30
return result
31
32
def tabulator(self):
33
if not self.children:
34
return []
35
result = []
36
for k, v in self.children.items():
37
children = v.tabulator()
38
feature = k
39
if v.entry != "":
40
link = (
41
"<a href='%s' target='_blank'><i class='fa-solid fa-link' aria-label='link'></i></a>"
42
% v.entry
43
)
44
feature = "%s %s" % (link, k)
45
d = {"sort_key": k, "feature": feature, **v.tabulator_leaf()}
46
if children:
47
d["_children"] = children
48
result.append(d)
49
result.sort(key=lambda x: x["sort_key"])
50
return result
51
52
def depth(self):
53
if not self.children:
54
return 0
55
return max([v.depth() for v in self.children.values()]) + 1
56
57
def size(self):
58
if not self.children:
59
return 1
60
return sum([v.size() for v in self.children.values()])
61
62
def walk(self, visitor, path=None):
63
if path is None:
64
path = []
65
visitor(self, path)
66
for k, v in self.children.items():
67
v.walk(visitor, path + [k])
68
69
70
def extract_metadata_from_file(file):
71
with open(file, "r") as f:
72
lines = f.readlines()
73
start = None
74
end = None
75
for i, line in enumerate(lines):
76
if line.strip() == "---":
77
if start is None:
78
start = i
79
else:
80
end = i
81
metadata = yaml.load(
82
"".join(lines[start + 1 : end]), Loader=yaml.SafeLoader
83
)
84
return metadata
85
raise ValueError("No metadata found in file %s" % file)
86
87
88
def table_cell(entry, _feature, _format_name, format_config):
89
if type(format_config) == str:
90
format_config = {}
91
result = []
92
quality = format_config.get("quality", "unknown")
93
if quality is not None:
94
if type(quality) == str:
95
quality = quality.lower()
96
qualities = {
97
-1: "&#x1F6AB;",
98
0: "&#x26A0;",
99
1: "&#x2713;",
100
2: "&#x2713;&#x2713;",
101
"unknown": "&#x2753;",
102
"na": "NA",
103
}
104
colors = {
105
-1: "bad",
106
0: "ok",
107
1: "good",
108
2: "good",
109
"unknown": "unknown",
110
"na": "na",
111
}
112
color = colors[quality]
113
quality_icon = qualities.get(quality, "&#x2753;")
114
result.append(f"<span class='{color}'>{quality_icon}</span>")
115
comment = format_config.get("comment", None)
116
if comment is not None:
117
# This is going to be an accessibility problem
118
result.append(f"<span title='{comment}'>&#x1F4AC;</span>")
119
return "".join(result)
120
121
122
def compute_trie(detailed=False):
123
trie = Trie()
124
pattern = "qmd-files/**/*.qmd" if detailed else "qmd-files/**/document.qmd"
125
for entry in pathlib.Path(".").glob(pattern):
126
if detailed:
127
feature = entry.parts[1:]
128
else:
129
feature = entry.parts[1:-1]
130
front_matter = extract_metadata_from_file(entry)
131
try:
132
format = front_matter["format"]
133
except KeyError:
134
raise Exception("No format found in %s" % entry)
135
for format_name, format_config in format.items():
136
trie.insert(
137
feature,
138
{
139
"feature": "/".join(feature),
140
"format": format_name,
141
"entry": entry,
142
"format_config": format_config,
143
"table_cell": table_cell(
144
entry, feature, format_name, format_config
145
),
146
},
147
)
148
return trie
149
150
151
def render_features_formats_data(trie=None):
152
if trie is None:
153
trie = compute_trie()
154
entries = trie.tabulator()
155
return (
156
"```{=html}\n<script type='text/javascript'>\nvar tableData = %s;\n</script>\n```\n"
157
% json.dumps(entries, indent=2)
158
)
159
160
161
def compute_quality_summary(trie=None):
162
if trie is None:
163
trie = compute_trie()
164
quality_summary = {"unknown": 0, -1: 0, 0: 0, 1: 0, 2: 0, "na": 0}
165
n_rows = 0
166
167
def visit(node, _path):
168
nonlocal n_rows
169
if not node.children or len(node.values):
170
n_rows = n_rows + 1
171
for v in node.values:
172
config = v["format_config"]
173
if type(config) == str:
174
config = {}
175
quality = config.get("quality", "unknown")
176
if type(quality) == str:
177
quality = quality.lower()
178
if quality_summary.get(quality) is None:
179
raise ValueError("Invalid quality value %s" % quality)
180
quality_summary[quality] += 1
181
182
trie.walk(visit)
183
return {"n_rows": n_rows, "quality": quality_summary}
184
185