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:
12 from docutils
.parsers
.rst
import Directive
13 from docutils
import nodes
14 from sphinx
.util
import logging
16 logger
= logging
.getLogger(__name__
)
19 class CephReleases(Directive
):
21 required_arguments
= 2
22 optional_arguments
= 0
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
)
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
),
42 tgroup
= nodes
.tgroup(cols
=3)
46 nodes
.colspec(colwidth
=30, colname
='c'+str(idx
))
47 for idx
, _
in enumerate(range(4)))
51 row_node
= nodes
.row()
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"])
58 releases
= releases
.items()
59 releases
= sorted(releases
, key
=lambda t
: t
[0], reverse
=True)
65 for code_name
, info
in releases
:
66 actual_eol
= info
.get("actual_eol", None)
69 if actual_eol
and actual_eol
<= datetime
.datetime
.now().date():
78 para
= nodes
.paragraph(text
=f
"`{code_name.title()} <{code_name}>`_")
79 sphinx
.util
.nodes
.nested_parse_with_titles(
80 self
.state
, para
, entry
)
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]
90 para
= nodes
.paragraph(text
="{}".format(
91 oldest_release
["released"]))
96 if newest_release
.get("skip_ref", False):
97 para
= nodes
.paragraph(text
="{}".format(
98 newest_release
["version"]))
100 para
= nodes
.paragraph(text
="`{}`_".format(
101 newest_release
["version"]))
102 sphinx
.util
.nodes
.nested_parse_with_titles(
103 self
.state
, para
, entry
)
107 entry
= nodes
.entry()
109 para
= nodes
.paragraph(text
=info
.get("target_eol", '--'))
111 para
= nodes
.paragraph(text
=info
.get('actual_eol', '--'))
122 RELEASES_TEMPLATE
= '''
126 dateFormat YYYY-MM-DD
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
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
139 class ReleasesGantt(Directive
):
141 required_arguments
= 1
142 optional_arguments
= 0
143 final_argument_whitespace
= False
145 template
= jinja2
.Environment().from_string(RELEASES_TEMPLATE
)
147 def _render_time_line(self
, filename
):
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}'
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']
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
)
174 active_releases
.append(release
)
175 rendered
= self
.template
.render(active_releases
=active_releases
,
176 archived_releases
=archived_releases
)
177 return rendered
.splitlines()
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
)
192 class CephTimeline(Directive
):
194 required_arguments
= 3
195 optional_arguments
= 0
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
)
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
),
212 display_releases
= self
.arguments
[1:]
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)))
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)))
229 timeline
= sorted(timeline
, key
=lambda t
: t
[0], reverse
=True)
231 table
= nodes
.table()
232 tgroup
= nodes
.tgroup(cols
=3)
235 columns
= ["Date"] + display_releases
237 nodes
.colspec(colwidth
=30, colname
='c'+str(idx
))
238 for idx
, _
in enumerate(range(len(columns
))))
240 thead
= nodes
.thead()
242 row_node
= nodes
.row()
245 entry
= nodes
.entry()
246 if col
.lower() in ["date", "development"]:
247 para
= nodes
.paragraph(text
=col
.title())
249 para
= nodes
.paragraph(text
=f
"`{col.title()} <{col}>`_".format(col
))
250 sphinx
.util
.nodes
.nested_parse_with_titles(
251 self
.state
, para
, entry
)
254 tbody
= nodes
.tbody()
258 for row_info
in timeline
:
261 entry
= nodes
.entry()
262 para
= nodes
.paragraph(text
=row_info
[0])
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])
272 para
= nodes
.paragraph(text
="`{}`_".format(row_info
[2]))
273 sphinx
.util
.nodes
.nested_parse_with_titles(
274 self
.state
, para
, entry
)
276 para
= nodes
.paragraph(text
="--")
286 TIMELINE_TEMPLATE
= '''
290 dateFormat YYYY-MM-DD
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
300 End of life (estimated): crit, {{ releases[display_release].target_eol }},4d
302 {% for release in releases[display_release].releases | sort(attribute='released', reverse=True) %}
303 {{ release.version }}: milestone, done, {{ release.released }},0d
309 class TimeLineGantt(Directive
):
311 required_arguments
= 2
312 optional_arguments
= 0
313 final_argument_whitespace
= True
315 template
= jinja2
.Environment().from_string(TIMELINE_TEMPLATE
)
317 def _render_time_line(self
, filename
, display_releases
):
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}'
325 rendered
= self
.template
.render(display_releases
=display_releases
,
327 return rendered
.splitlines()
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
)
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
)
349 'parallel_read_safe': True,
350 'parallel_write_safe': True