]> git.proxmox.com Git - ceph.git/blob - ceph/doc/_ext/ceph_releases.py
update ceph source to reef 18.1.2
[ceph.git] / ceph / doc / _ext / ceph_releases.py
1 # cobbled together from:
2 # https://github.com/sphinx-contrib/documentedlist/blob/master/sphinxcontrib/documentedlist.py
3 # https://github.com/sphinx-doc/sphinx/blob/v1.6.3/sphinx/ext/graphviz.py
4 # https://github.com/thewtex/sphinx-contrib/blob/master/exceltable/sphinxcontrib/exceltable.py
5 # https://bitbucket.org/prometheus/sphinxcontrib-htsql/src/331a542c29a102eec9f8cba44797e53a49de2a49/sphinxcontrib/htsql.py?at=default&fileviewer=file-view-default
6 # into the glory that follows:
7 import json
8 import yaml
9 import jinja2
10 import sphinx
11 import datetime
12 from docutils.parsers.rst import Directive
13 from docutils import nodes
14 from sphinx.util import logging
15
16 logger = logging.getLogger(__name__)
17
18
19 class CephReleases(Directive):
20 has_content = False
21 required_arguments = 2
22 optional_arguments = 0
23 option_spec = {}
24
25 def run(self):
26 filename = self.arguments[0]
27 current = self.arguments[1] == 'current'
28 document = self.state.document
29 env = document.settings.env
30 rel_filename, filename = env.relfn2path(filename)
31 env.note_dependency(filename)
32 try:
33 with open(filename, 'r') as fp:
34 releases = yaml.safe_load(fp)
35 releases = releases["releases"]
36 except Exception as e:
37 return [document.reporter.warning(
38 "Failed to open Ceph releases file {}: {}".format(filename, e),
39 line=self.lineno)]
40
41 table = nodes.table()
42 tgroup = nodes.tgroup(cols=3)
43 table += tgroup
44
45 tgroup.extend(
46 nodes.colspec(colwidth=30, colname='c'+str(idx))
47 for idx, _ in enumerate(range(4)))
48
49 thead = nodes.thead()
50 tgroup += thead
51 row_node = nodes.row()
52 thead += row_node
53 row_node.extend(
54 nodes.entry(h, nodes.paragraph(text=h))
55 for h in ["Name", "Initial release", "Latest",
56 "End of life (estimated)" if current else "End of life"])
57
58 releases = releases.items()
59 releases = sorted(releases, key=lambda t: t[0], reverse=True)
60
61 tbody = nodes.tbody()
62 tgroup += tbody
63
64 rows = []
65 for code_name, info in releases:
66 actual_eol = info.get("actual_eol", None)
67
68 if current:
69 if actual_eol and actual_eol <= datetime.datetime.now().date():
70 continue
71 else:
72 if not actual_eol:
73 continue
74
75 trow = nodes.row()
76
77 entry = nodes.entry()
78 para = nodes.paragraph(text=f"`{code_name.title()} <{code_name}>`_")
79 sphinx.util.nodes.nested_parse_with_titles(
80 self.state, para, entry)
81 #entry += para
82 trow += entry
83
84 sorted_releases = sorted(info["releases"],
85 key=lambda t: [t["released"]] + list(map(lambda v: int(v), t["version"].split("."))))
86 oldest_release = sorted_releases[0]
87 newest_release = sorted_releases[-1]
88
89 entry = nodes.entry()
90 para = nodes.paragraph(text="{}".format(
91 oldest_release["released"]))
92 entry += para
93 trow += entry
94
95 entry = nodes.entry()
96 if newest_release.get("skip_ref", False):
97 para = nodes.paragraph(text="{}".format(
98 newest_release["version"]))
99 else:
100 para = nodes.paragraph(text="`{}`_".format(
101 newest_release["version"]))
102 sphinx.util.nodes.nested_parse_with_titles(
103 self.state, para, entry)
104 #entry += para
105 trow += entry
106
107 entry = nodes.entry()
108 if current:
109 para = nodes.paragraph(text=info.get("target_eol", '--'))
110 else:
111 para = nodes.paragraph(text=info.get('actual_eol', '--'))
112 entry += para
113 trow += entry
114
115 rows.append(trow)
116
117 tbody.extend(rows)
118
119 return [table]
120
121
122 RELEASES_TEMPLATE = '''
123 .. mermaid::
124
125 gantt
126 dateFormat YYYY-MM-DD
127 axisFormat %Y
128 section Active Releases
129 {% for release in active_releases %}
130 {{ release.code_name }} (latest {{ release.last_version }}): done, {{ release.debute_date }},{{ release.lifetime }}d
131 {% endfor %}
132 section Archived Releases
133 {% for release in archived_releases %}
134 {{ release.code_name }} (latest {{ release.last_version }}): done, {{ release.debute_date }},{{ release.lifetime }}d
135 {% endfor %}
136 '''
137
138
139 class ReleasesGantt(Directive):
140 has_content = True
141 required_arguments = 1
142 optional_arguments = 0
143 final_argument_whitespace = False
144
145 template = jinja2.Environment().from_string(RELEASES_TEMPLATE)
146
147 def _render_time_line(self, filename):
148 try:
149 with open(filename) as f:
150 releases = yaml.safe_load(f)['releases']
151 except Exception as e:
152 message = f'Unable read release file: "{filename}": {e}'
153 self.error(message)
154
155 active_releases = []
156 archived_releases = []
157 # just update `releases` with extracted info
158 for code_name, info in releases.items():
159 last_release = info['releases'][0]
160 first_release = info['releases'][-1]
161 last_version = last_release['version']
162 debute_date = first_release['released']
163 if 'actual_eol' in info:
164 lifetime = info['actual_eol'] - first_release['released']
165 else:
166 lifetime = info['target_eol'] - first_release['released']
167 release = dict(code_name=code_name,
168 last_version=last_version,
169 debute_date=debute_date,
170 lifetime=lifetime.days)
171 if 'actual_eol' in info:
172 archived_releases.append(release)
173 else:
174 active_releases.append(release)
175 rendered = self.template.render(active_releases=active_releases,
176 archived_releases=archived_releases)
177 return rendered.splitlines()
178
179 def run(self):
180 filename = self.arguments[0]
181 document = self.state.document
182 env = document.settings.env
183 rel_filename, filename = env.relfn2path(filename)
184 env.note_dependency(filename)
185 lines = self._render_time_line(filename)
186 lineno = self.lineno - self.state_machine.input_offset - 1
187 source = self.state_machine.input_lines.source(lineno)
188 self.state_machine.insert_input(lines, source)
189 return []
190
191
192 class CephTimeline(Directive):
193 has_content = False
194 required_arguments = 3
195 optional_arguments = 0
196 option_spec = {}
197
198 def run(self):
199 filename = self.arguments[0]
200 document = self.state.document
201 env = document.settings.env
202 rel_filename, filename = env.relfn2path(filename)
203 env.note_dependency(filename)
204 try:
205 with open(filename, 'r') as fp:
206 releases = yaml.safe_load(fp)
207 except Exception as e:
208 return [document.reporter.warning(
209 "Failed to open Ceph releases file {}: {}".format(filename, e),
210 line=self.lineno)]
211
212 display_releases = self.arguments[1:]
213
214 timeline = []
215 for code_name, info in releases["releases"].items():
216 if code_name in display_releases:
217 for release in info.get("releases", []):
218 released = release["released"]
219 timeline.append((released, code_name, release["version"],
220 release.get("skip_ref", False)))
221
222 assert "development" not in releases["releases"]
223 if "development" in display_releases:
224 for release in releases["development"]["releases"]:
225 released = release["released"]
226 timeline.append((released, "development", release["version"],
227 release.get("skip_ref", False)))
228
229 timeline = sorted(timeline, key=lambda t: t[0], reverse=True)
230
231 table = nodes.table()
232 tgroup = nodes.tgroup(cols=3)
233 table += tgroup
234
235 columns = ["Date"] + display_releases
236 tgroup.extend(
237 nodes.colspec(colwidth=30, colname='c'+str(idx))
238 for idx, _ in enumerate(range(len(columns))))
239
240 thead = nodes.thead()
241 tgroup += thead
242 row_node = nodes.row()
243 thead += row_node
244 for col in columns:
245 entry = nodes.entry()
246 if col.lower() in ["date", "development"]:
247 para = nodes.paragraph(text=col.title())
248 else:
249 para = nodes.paragraph(text=f"`{col.title()} <{col}>`_".format(col))
250 sphinx.util.nodes.nested_parse_with_titles(
251 self.state, para, entry)
252 row_node += entry
253
254 tbody = nodes.tbody()
255 tgroup += tbody
256
257 rows = []
258 for row_info in timeline:
259 trow = nodes.row()
260
261 entry = nodes.entry()
262 para = nodes.paragraph(text=row_info[0])
263 entry += para
264 trow += entry
265
266 for release in display_releases:
267 entry = nodes.entry()
268 if row_info[1] == release:
269 if row_info[3]: # if skip ref
270 para = nodes.paragraph(text=row_info[2])
271 else:
272 para = nodes.paragraph(text="`{}`_".format(row_info[2]))
273 sphinx.util.nodes.nested_parse_with_titles(
274 self.state, para, entry)
275 else:
276 para = nodes.paragraph(text="--")
277 entry += para
278 trow += entry
279 rows.append(trow)
280
281 tbody.extend(rows)
282
283 return [table]
284
285
286 TIMELINE_TEMPLATE = '''
287 .. mermaid::
288
289 gantt
290 dateFormat YYYY-MM-DD
291 axisFormat %Y-%m
292 {% if title %}
293 title {{title}}
294 {% endif %}
295 {% for display_release in display_releases %}
296 section {{ display_release }}
297 {%if releases[display_release].actual_eol %}
298 End of life: crit, {{ releases[display_release].actual_eol }},4d
299 {% else %}
300 End of life (estimated): crit, {{ releases[display_release].target_eol }},4d
301 {% endif %}
302 {% for release in releases[display_release].releases | sort(attribute='released', reverse=True) %}
303 {{ release.version }}: milestone, done, {{ release.released }},0d
304 {% endfor %}
305 {% endfor %}
306 '''
307
308
309 class TimeLineGantt(Directive):
310 has_content = True
311 required_arguments = 2
312 optional_arguments = 0
313 final_argument_whitespace = True
314
315 template = jinja2.Environment().from_string(TIMELINE_TEMPLATE)
316
317 def _render_time_line(self, filename, display_releases):
318 try:
319 with open(filename) as f:
320 releases = yaml.safe_load(f)['releases']
321 except Exception as e:
322 message = f'Unable read release file: "{filename}": {e}'
323 self.error(message)
324
325 rendered = self.template.render(display_releases=display_releases,
326 releases=releases)
327 return rendered.splitlines()
328
329 def run(self):
330 filename = self.arguments[0]
331 display_releases = self.arguments[1].split()
332 document = self.state.document
333 env = document.settings.env
334 rel_filename, filename = env.relfn2path(filename)
335 env.note_dependency(filename)
336 lines = self._render_time_line(filename, display_releases)
337 lineno = self.lineno - self.state_machine.input_offset - 1
338 source = self.state_machine.input_lines.source(lineno)
339 self.state_machine.insert_input(lines, source)
340 return []
341
342
343 def setup(app):
344 app.add_directive('ceph_releases', CephReleases)
345 app.add_directive('ceph_releases_gantt', ReleasesGantt)
346 app.add_directive('ceph_timeline', CephTimeline)
347 app.add_directive('ceph_timeline_gantt', TimeLineGantt)
348 return {
349 'parallel_read_safe': True,
350 'parallel_write_safe': True
351 }