Browse Source

Import directives/roles from zuul

This imports the current directives from Zuul itself, and adds
an example doc page that exercises them all so this repo is more
self-testing.

Also, use python3 by default to ensure we remain py3 compat.

Change-Id: Ie5b3cedd5e8dfaf0763d09a901fc9ba0e5b63683
James E. Blair 1 year ago
parent
commit
27322342f2
5 changed files with 409 additions and 42 deletions
  1. 2
    0
      doc/source/conf.py
  2. 132
    0
      doc/source/examples.rst
  3. 1
    1
      doc/source/index.rst
  4. 1
    0
      tox.ini
  5. 273
    41
      zuul_sphinx/zuul.py

+ 2
- 0
doc/source/conf.py View File

@@ -30,6 +30,8 @@ extensions = [
30 30
 # text edit cycles.
31 31
 # execute "export SPHINX_DEBUG=1" in your terminal to disable
32 32
 
33
+primary_domain = 'zuul'
34
+
33 35
 # The suffix of source filenames.
34 36
 source_suffix = '.rst'
35 37
 

+ 132
- 0
doc/source/examples.rst View File

@@ -0,0 +1,132 @@
1
+Examples
2
+========
3
+
4
+Jobs
5
+----
6
+
7
+.. job:: example-job
8
+
9
+   This is an example job.
10
+
11
+   .. var:: foo
12
+
13
+      This is a variable used by this job.
14
+
15
+      .. var:: bar
16
+
17
+         This is a sub key.
18
+
19
+   .. var:: items
20
+      :type: list
21
+
22
+      This variable is a list.
23
+
24
+      .. var:: baz
25
+
26
+         This is an item in a list.
27
+
28
+.. job:: example-job
29
+   :variant: stable
30
+
31
+   This is a variant of :job:`example-job` which runs on stable branches.
32
+
33
+This is a job role: :job:`example-job`
34
+
35
+This is a job variable role: :var:`example-job.foo.bar`
36
+
37
+Roles
38
+-----
39
+
40
+.. role:: example-role
41
+
42
+   This is an example role.
43
+
44
+   **Role Variables**
45
+
46
+   .. var:: foo
47
+
48
+      This is a variable used by this role.
49
+
50
+      .. var:: bar
51
+
52
+         This is a sub key.
53
+
54
+   .. var:: items
55
+      :type: list
56
+
57
+      This variable is a list.
58
+
59
+      .. var:: baz
60
+
61
+         This is an item in a list.
62
+
63
+This is an (Ansible) role (Sphinx) role: :role:`example-role`
64
+
65
+This is an (Ansible) role variable (Sphinx) role: :var:`example-role.items.baz`
66
+
67
+Configuration Attributes
68
+------------------------
69
+
70
+.. attr:: example-attr
71
+   :required:
72
+
73
+   This is an example configuration attribute.
74
+
75
+   .. attr:: foo
76
+      :default: bar
77
+
78
+      A sub attribute.
79
+
80
+      .. value:: bar
81
+
82
+         An attribute value.
83
+
84
+      .. value:: baz
85
+
86
+         Another attribute value.
87
+
88
+This is an attribute role: :attr:`example-attr.foo`
89
+
90
+This is an attribute value role: :value:`example-attr.foo.bar`
91
+
92
+
93
+Job Variables
94
+-------------
95
+
96
+.. var:: example-variable
97
+
98
+   This is an example variable.
99
+
100
+   .. var:: foo
101
+
102
+      This is a variable.
103
+
104
+      .. var:: bar
105
+
106
+         This is a sub key.
107
+
108
+   .. var:: items
109
+      :type: list
110
+
111
+      This variable is a list.
112
+
113
+      .. var:: baz
114
+
115
+         This is an item in a list.
116
+
117
+This is a variable role: :var:`example-variable.items.baz`
118
+
119
+
120
+Statistics
121
+----------
122
+
123
+.. stat:: example-stat
124
+
125
+   This is an example statistic.
126
+
127
+   .. stat:: foo
128
+      :type: counter
129
+
130
+      A sub stat.
131
+
132
+This is a statistics role: :stat:`example-stat.foo`

+ 1
- 1
doc/source/index.rst View File

@@ -1,9 +1,9 @@
1 1
 .. include:: ../../README.rst
2 2
 
3
-
4 3
 .. toctree::
5 4
    :maxdepth: 2
6 5
 
6
+   examples
7 7
 
8 8
 Indices and tables
9 9
 ==================

+ 1
- 0
tox.ini View File

@@ -4,6 +4,7 @@ skipsdist = True
4 4
 envlist = pep8
5 5
 
6 6
 [testenv]
7
+basepython = python3
7 8
 install_command = pip install {opts} {packages}
8 9
 deps = -r{toxinidir}/requirements.txt
9 10
        -r{toxinidir}/test-requirements.txt

+ 273
- 41
zuul_sphinx/zuul.py View File

@@ -15,7 +15,10 @@
15 15
 from sphinx import addnodes
16 16
 from docutils.parsers.rst import Directive
17 17
 from sphinx.domains import Domain, ObjType
18
+from sphinx.roles import XRefRole
18 19
 from sphinx.directives import ObjectDescription
20
+from sphinx.util.nodes import make_refnode
21
+from docutils import nodes
19 22
 import os
20 23
 
21 24
 import yaml
@@ -26,7 +29,7 @@ class Layout(object):
26 29
         self.jobs = []
27 30
 
28 31
 
29
-class BaseZuulDirective(Directive):
32
+class ZuulDirective(Directive):
30 33
     has_content = True
31 34
 
32 35
     def find_zuul_yaml(self):
@@ -117,31 +120,235 @@ class BaseZuulDirective(Directive):
117 120
         return lines
118 121
 
119 122
 
120
-class ZuulJobDirective(BaseZuulDirective, ObjectDescription):
121
-    option_spec = {
122
-        'variant': lambda x: x,
123
+class ZuulObjectDescription(ZuulDirective, ObjectDescription):
124
+    object_names = {
125
+        'attr': 'attribute',
126
+        'var': 'variable',
123 127
     }
124 128
 
125
-    def handle_signature(self, sig, signode):
126
-        signode += addnodes.desc_name(sig, sig)
127
-        return sig
129
+    def get_path(self):
130
+        return self.env.ref_context.get('zuul:attr_path', [])
131
+
132
+    def get_display_path(self):
133
+        return self.env.ref_context.get('zuul:display_attr_path', [])
134
+
135
+    @property
136
+    def parent_pathname(self):
137
+        return '.'.join(self.get_display_path())
138
+
139
+    @property
140
+    def full_pathname(self):
141
+        name = self.names[-1].lower()
142
+        return '.'.join(self.get_path() + [name])
128 143
 
129 144
     def add_target_and_index(self, name, sig, signode):
130
-        targetname = self.objtype + '-' + name
131
-        if 'variant' in self.options:
132
-            targetname += '-' + self.options['variant']
145
+        targetname = self.objtype + '-' + self.full_pathname
133 146
         if targetname not in self.state.document.ids:
134 147
             signode['names'].append(targetname)
135 148
             signode['ids'].append(targetname)
136 149
             signode['first'] = (not self.names)
137 150
             self.state.document.note_explicit_target(signode)
138
-
139
-        indextext = '%s (%s)' % (name, self.objtype)
151
+            objects = self.env.domaindata['zuul']['objects']
152
+            if targetname in objects:
153
+                self.state_machine.reporter.warning(
154
+                    'duplicate object description of %s, ' % targetname +
155
+                    'other instance in ' +
156
+                    self.env.doc2path(objects[targetname][0]) +
157
+                    ', use :noindex: for one of them',
158
+                    line=self.lineno)
159
+            objects[targetname] = (self.env.docname, self.objtype)
160
+
161
+        objname = self.object_names.get(self.objtype, self.objtype)
162
+        if self.parent_pathname:
163
+            indextext = '%s (%s of %s)' % (name, objname,
164
+                                           self.parent_pathname)
165
+        else:
166
+            indextext = '%s (%s)' % (name, objname)
140 167
         self.indexnode['entries'].append(('single', indextext,
141 168
                                           targetname, '', None))
142 169
 
143 170
 
144
-class ZuulAutoJobDirective(BaseZuulDirective):
171
+######################################################################
172
+#
173
+# Object description directives
174
+#
175
+
176
+class ZuulJobDirective(ZuulObjectDescription):
177
+    option_spec = {
178
+        'variant': lambda x: x,
179
+    }
180
+
181
+    def before_content(self):
182
+        path = self.env.ref_context.setdefault('zuul:attr_path', [])
183
+        element = self.names[-1]
184
+        path.append(element)
185
+
186
+    def after_content(self):
187
+        path = self.env.ref_context.get('zuul:attr_path')
188
+        if path:
189
+            path.pop()
190
+
191
+    def handle_signature(self, sig, signode):
192
+        signode += addnodes.desc_name(sig, sig)
193
+        return sig
194
+
195
+
196
+class ZuulRoleDirective(ZuulObjectDescription):
197
+    def before_content(self):
198
+        path = self.env.ref_context.setdefault('zuul:attr_path', [])
199
+        element = self.names[-1]
200
+        path.append(element)
201
+
202
+    def after_content(self):
203
+        path = self.env.ref_context.get('zuul:attr_path')
204
+        if path:
205
+            path.pop()
206
+
207
+    def handle_signature(self, sig, signode):
208
+        signode += addnodes.desc_name(sig, sig)
209
+        return sig
210
+
211
+
212
+class ZuulAttrDirective(ZuulObjectDescription):
213
+    has_content = True
214
+
215
+    option_spec = {
216
+        'required': lambda x: x,
217
+        'default': lambda x: x,
218
+        'noindex': lambda x: x,
219
+    }
220
+
221
+    def before_content(self):
222
+        path = self.env.ref_context.setdefault('zuul:attr_path', [])
223
+        path.append(self.names[-1])
224
+        path = self.env.ref_context.setdefault('zuul:display_attr_path', [])
225
+        path.append(self.names[-1])
226
+
227
+    def after_content(self):
228
+        path = self.env.ref_context.get('zuul:attr_path')
229
+        if path:
230
+            path.pop()
231
+        path = self.env.ref_context.get('zuul:display_attr_path')
232
+        if path:
233
+            path.pop()
234
+
235
+    def handle_signature(self, sig, signode):
236
+        path = self.get_display_path()
237
+        signode['is_multiline'] = True
238
+        line = addnodes.desc_signature_line()
239
+        line['add_permalink'] = True
240
+        for x in path:
241
+            line += addnodes.desc_addname(x + '.', x + '.')
242
+        line += addnodes.desc_name(sig, sig)
243
+        if 'required' in self.options:
244
+            line += addnodes.desc_annotation(' (required)', ' (required)')
245
+        signode += line
246
+        if 'default' in self.options:
247
+            line = addnodes.desc_signature_line()
248
+            line += addnodes.desc_type('Default: ', 'Default: ')
249
+            line += nodes.literal(self.options['default'],
250
+                                  self.options['default'])
251
+            signode += line
252
+        return sig
253
+
254
+
255
+class ZuulValueDirective(ZuulObjectDescription):
256
+    has_content = True
257
+
258
+    def handle_signature(self, sig, signode):
259
+        signode += addnodes.desc_name(sig, sig)
260
+        return sig
261
+
262
+
263
+class ZuulVarDirective(ZuulObjectDescription):
264
+    has_content = True
265
+
266
+    option_spec = {
267
+        'type': lambda x: x,
268
+        'hidden': lambda x: x,
269
+        'noindex': lambda x: x,
270
+    }
271
+
272
+    type_map = {
273
+        'list': '[]',
274
+        'dict': '{}',
275
+    }
276
+
277
+    def get_type_str(self):
278
+        if 'type' in self.options:
279
+            return self.type_map[self.options['type']]
280
+        return ''
281
+
282
+    def before_content(self):
283
+        path = self.env.ref_context.setdefault('zuul:attr_path', [])
284
+        element = self.names[-1]
285
+        path.append(element)
286
+        path = self.env.ref_context.setdefault('zuul:display_attr_path', [])
287
+        element = self.names[-1] + self.get_type_str()
288
+        path.append(element)
289
+
290
+    def after_content(self):
291
+        path = self.env.ref_context.get('zuul:attr_path')
292
+        if path:
293
+            path.pop()
294
+        path = self.env.ref_context.get('zuul:display_attr_path')
295
+        if path:
296
+            path.pop()
297
+
298
+    def handle_signature(self, sig, signode):
299
+        if 'hidden' in self.options:
300
+            return sig
301
+        path = self.get_display_path()
302
+        for x in path:
303
+            signode += addnodes.desc_addname(x + '.', x + '.')
304
+        signode += addnodes.desc_name(sig, sig)
305
+        return sig
306
+
307
+
308
+class ZuulStatDirective(ZuulObjectDescription):
309
+    has_content = True
310
+
311
+    option_spec = {
312
+        'type': lambda x: x,
313
+        'hidden': lambda x: x,
314
+        'noindex': lambda x: x,
315
+    }
316
+
317
+    def before_content(self):
318
+        path = self.env.ref_context.setdefault('zuul:attr_path', [])
319
+        element = self.names[-1]
320
+        path.append(element)
321
+        path = self.env.ref_context.setdefault('zuul:display_attr_path', [])
322
+        element = self.names[-1]
323
+        path.append(element)
324
+
325
+    def after_content(self):
326
+        path = self.env.ref_context.get('zuul:attr_path')
327
+        if path:
328
+            path.pop()
329
+        path = self.env.ref_context.get('zuul:display_attr_path')
330
+        if path:
331
+            path.pop()
332
+
333
+    def handle_signature(self, sig, signode):
334
+        if 'hidden' in self.options:
335
+            return sig
336
+        path = self.get_display_path()
337
+        for x in path:
338
+            signode += addnodes.desc_addname(x + '.', x + '.')
339
+        signode += addnodes.desc_name(sig, sig)
340
+        if 'type' in self.options:
341
+            t = ' (%s)' % self.options['type']
342
+            signode += addnodes.desc_annotation(t, t)
343
+        return sig
344
+
345
+
346
+######################################################################
347
+#
348
+# Autodoc directives
349
+#
350
+
351
+class ZuulAutoJobDirective(ZuulDirective):
145 352
     def run(self):
146 353
         name = self.content[0]
147 354
         lines = self.generate_zuul_job_content(name)
@@ -149,7 +356,7 @@ class ZuulAutoJobDirective(BaseZuulDirective):
149 356
         return []
150 357
 
151 358
 
152
-class ZuulAutoJobsDirective(BaseZuulDirective):
359
+class ZuulAutoJobsDirective(ZuulDirective):
153 360
     has_content = False
154 361
 
155 362
     def run(self):
@@ -165,25 +372,7 @@ class ZuulAutoJobsDirective(BaseZuulDirective):
165 372
         return []
166 373
 
167 374
 
168
-class ZuulRoleDirective(BaseZuulDirective, ObjectDescription):
169
-    def handle_signature(self, sig, signode):
170
-        signode += addnodes.desc_name(sig, sig)
171
-        return sig
172
-
173
-    def add_target_and_index(self, name, sig, signode):
174
-        targetname = self.objtype + '-' + name
175
-        if targetname not in self.state.document.ids:
176
-            signode['names'].append(targetname)
177
-            signode['ids'].append(targetname)
178
-            signode['first'] = (not self.names)
179
-            self.state.document.note_explicit_target(signode)
180
-
181
-        indextext = '%s (%s)' % (name, self.objtype)
182
-        self.indexnode['entries'].append(('single', indextext,
183
-                                          targetname, '', None))
184
-
185
-
186
-class ZuulAutoRoleDirective(BaseZuulDirective):
375
+class ZuulAutoRoleDirective(ZuulDirective):
187 376
     def run(self):
188 377
         name = self.content[0]
189 378
         lines = self.generate_zuul_role_content(name)
@@ -191,7 +380,7 @@ class ZuulAutoRoleDirective(BaseZuulDirective):
191 380
         return []
192 381
 
193 382
 
194
-class ZuulAutoRolesDirective(BaseZuulDirective):
383
+class ZuulAutoRolesDirective(ZuulDirective):
195 384
     has_content = False
196 385
 
197 386
     def run(self):
@@ -202,29 +391,72 @@ class ZuulAutoRolesDirective(BaseZuulDirective):
202 391
         return []
203 392
 
204 393
 
394
+class ZuulAbbreviatedXRefRole(XRefRole):
395
+
396
+    def process_link(self, env, refnode, has_explicit_title, title,
397
+                     target):
398
+        title, target = super(ZuulAbbreviatedXRefRole, self).process_link(
399
+            env, refnode, has_explicit_title, title, target)
400
+        if not has_explicit_title:
401
+            title = title.split('.')[-1]
402
+        return title, target
403
+
404
+
205 405
 class ZuulDomain(Domain):
206 406
     name = 'zuul'
207 407
     label = 'Zuul'
208 408
 
209
-    object_types = {
210
-        'job': ObjType('job'),
211
-        'role': ObjType('role'),
212
-    }
213
-
214 409
     directives = {
410
+        # Object description directives
215 411
         'job': ZuulJobDirective,
412
+        'role': ZuulRoleDirective,
413
+        'attr': ZuulAttrDirective,
414
+        'value': ZuulValueDirective,
415
+        'var': ZuulVarDirective,
416
+        'stat': ZuulStatDirective,
417
+        # Autodoc directives
216 418
         'autojob': ZuulAutoJobDirective,
217 419
         'autojobs': ZuulAutoJobsDirective,
218
-        'role': ZuulRoleDirective,
219 420
         'autorole': ZuulAutoRoleDirective,
220 421
         'autoroles': ZuulAutoRolesDirective,
221 422
     }
222 423
 
424
+    roles = {
425
+        'job': XRefRole(innernodeclass=nodes.inline,  # type: ignore
426
+                        warn_dangling=True),
427
+        'role': XRefRole(innernodeclass=nodes.inline,  # type: ignore
428
+                         warn_dangling=True),
429
+        'attr': XRefRole(innernodeclass=nodes.inline,  # type: ignore
430
+                         warn_dangling=True),
431
+        'value': ZuulAbbreviatedXRefRole(
432
+            innernodeclass=nodes.inline,  # type: ignore
433
+            warn_dangling=True),
434
+        'var': XRefRole(innernodeclass=nodes.inline,  # type: ignore
435
+                        warn_dangling=True),
436
+        'stat': XRefRole(innernodeclass=nodes.inline,  # type: ignore
437
+                         warn_dangling=True),
438
+    }
439
+
223 440
     initial_data = {
224 441
         'layout': None,
225 442
         'layout_path': None,
226 443
         'role_paths': None,
227
-    }
444
+        'objects': {},
445
+    }  # type: Dict[str, Dict]
446
+
447
+    def resolve_xref(self, env, fromdocname, builder, type, target,
448
+                     node, contnode):
449
+        objects = self.data['objects']
450
+        name = type + '-' + target
451
+        obj = objects.get(name)
452
+        if obj:
453
+            return make_refnode(builder, fromdocname, obj[0], name,
454
+                                contnode, name)
455
+
456
+    def clear_doc(self, docname):
457
+        for fullname, (fn, _l) in list(self.data['objects'].items()):
458
+            if fn == docname:
459
+                del self.data['objects'][fullname]
228 460
 
229 461
 
230 462
 def setup(app):

Loading…
Cancel
Save