Upgrade to 1.46.0
[platform/upstream/nghttp2.git] / doc / _exts / rubydomain / rubydomain.py
1 # -*- coding: utf-8 -*-
2 """
3     sphinx.domains.ruby
4     ~~~~~~~~~~~~~~~~~~~
5
6     The Ruby domain.
7
8     :copyright: Copyright 2010 by SHIBUKAWA Yoshiki
9     :license: BSD, see LICENSE for details.
10 """
11
12 import re
13
14 from docutils import nodes
15 from docutils.parsers.rst import directives
16 from docutils.parsers.rst import Directive
17
18 from sphinx import addnodes
19 from sphinx import version_info
20 from sphinx.roles import XRefRole
21 from sphinx.locale import _
22 from sphinx.domains import Domain, ObjType, Index
23 from sphinx.directives import ObjectDescription
24 from sphinx.util.nodes import make_refnode
25 from sphinx.util.docfields import Field, GroupedField, TypedField
26
27 # REs for Ruby signatures
28 rb_sig_re = re.compile(
29     r'''^ ([\w.]*\.)?            # class name(s)
30           (\$?\w+\??!?)  \s*     # thing name
31           (?: \((.*)\)           # optional: arguments
32            (?:\s* -> \s* (.*))?  #           return annotation
33           )? $                   # and nothing more
34           ''', re.VERBOSE)
35
36 rb_paramlist_re = re.compile(r'([\[\],])')  # split at '[', ']' and ','
37
38 separators = {
39   'method':'#', 'attr_reader':'#', 'attr_writer':'#', 'attr_accessor':'#',
40   'function':'.', 'classmethod':'.', 'class':'::', 'module':'::',
41   'global':'', 'const':'::'}
42
43 rb_separator = re.compile(r"(?:\w+)?(?:::)?(?:\.)?(?:#)?")
44
45
46 def _iteritems(d):
47
48     for k in d:
49         yield k, d[k]
50
51
52 def ruby_rsplit(fullname):
53     items = [item for item in rb_separator.findall(fullname)]
54     return ''.join(items[:-2]), items[-1]
55
56
57 class RubyObject(ObjectDescription):
58     """
59     Description of a general Ruby object.
60     """
61     option_spec = {
62         'noindex': directives.flag,
63         'module': directives.unchanged,
64     }
65
66     doc_field_types = [
67         TypedField('parameter', label=_('Parameters'),
68                    names=('param', 'parameter', 'arg', 'argument'),
69                    typerolename='obj', typenames=('paramtype', 'type')),
70         TypedField('variable', label=_('Variables'), rolename='obj',
71                    names=('var', 'ivar', 'cvar'),
72                    typerolename='obj', typenames=('vartype',)),
73         GroupedField('exceptions', label=_('Raises'), rolename='exc',
74                      names=('raises', 'raise', 'exception', 'except'),
75                      can_collapse=True),
76         Field('returnvalue', label=_('Returns'), has_arg=False,
77               names=('returns', 'return')),
78         Field('returntype', label=_('Return type'), has_arg=False,
79               names=('rtype',)),
80     ]
81
82     def get_signature_prefix(self, sig):
83         """
84         May return a prefix to put before the object name in the signature.
85         """
86         return ''
87
88     def needs_arglist(self):
89         """
90         May return true if an empty argument list is to be generated even if
91         the document contains none.
92         """
93         return False
94
95     def handle_signature(self, sig, signode):
96         """
97         Transform a Ruby signature into RST nodes.
98         Returns (fully qualified name of the thing, classname if any).
99
100         If inside a class, the current class name is handled intelligently:
101         * it is stripped from the displayed name if present
102         * it is added to the full name (return value) if not present
103         """
104         m = rb_sig_re.match(sig)
105         if m is None:
106             raise ValueError
107         name_prefix, name, arglist, retann = m.groups()
108         if not name_prefix:
109             name_prefix = ""
110         # determine module and class name (if applicable), as well as full name
111         modname = self.options.get(
112             'module', self.env.temp_data.get('rb:module'))
113         classname = self.env.temp_data.get('rb:class')
114         if self.objtype == 'global':
115             add_module = False
116             modname = None
117             classname = None
118             fullname = name
119         elif classname:
120             add_module = False
121             if name_prefix and name_prefix.startswith(classname):
122                 fullname = name_prefix + name
123                 # class name is given again in the signature
124                 name_prefix = name_prefix[len(classname):].lstrip('.')
125             else:
126                 separator = separators[self.objtype]
127                 fullname = classname + separator + name_prefix + name
128         else:
129             add_module = True
130             if name_prefix:
131                 classname = name_prefix.rstrip('.')
132                 fullname = name_prefix + name
133             else:
134                 classname = ''
135                 fullname = name
136
137         signode['module'] = modname
138         signode['class'] = self.class_name = classname
139         signode['fullname'] = fullname
140
141         sig_prefix = self.get_signature_prefix(sig)
142         if sig_prefix:
143             signode += addnodes.desc_annotation(sig_prefix, sig_prefix)
144
145         if name_prefix:
146             signode += addnodes.desc_addname(name_prefix, name_prefix)
147         # exceptions are a special case, since they are documented in the
148         # 'exceptions' module.
149         elif add_module and self.env.config.add_module_names:
150             if self.objtype == 'global':
151                 nodetext = ''
152                 signode += addnodes.desc_addname(nodetext, nodetext)
153             else:
154                 modname = self.options.get(
155                     'module', self.env.temp_data.get('rb:module'))
156                 if modname and modname != 'exceptions':
157                     nodetext = modname + separators[self.objtype]
158                     signode += addnodes.desc_addname(nodetext, nodetext)
159
160         signode += addnodes.desc_name(name, name)
161         if not arglist:
162             if self.needs_arglist():
163                 # for callables, add an empty parameter list
164                 signode += addnodes.desc_parameterlist()
165             if retann:
166                 signode += addnodes.desc_returns(retann, retann)
167             return fullname, name_prefix
168         signode += addnodes.desc_parameterlist()
169
170         stack = [signode[-1]]
171         for token in rb_paramlist_re.split(arglist):
172             if token == '[':
173                 opt = addnodes.desc_optional()
174                 stack[-1] += opt
175                 stack.append(opt)
176             elif token == ']':
177                 try:
178                     stack.pop()
179                 except IndexError:
180                     raise ValueError
181             elif not token or token == ',' or token.isspace():
182                 pass
183             else:
184                 token = token.strip()
185                 stack[-1] += addnodes.desc_parameter(token, token)
186         if len(stack) != 1:
187             raise ValueError
188         if retann:
189             signode += addnodes.desc_returns(retann, retann)
190         return fullname, name_prefix
191
192     def get_index_text(self, modname, name):
193         """
194         Return the text for the index entry of the object.
195         """
196         raise NotImplementedError('must be implemented in subclasses')
197
198     def _is_class_member(self):
199         return self.objtype.endswith('method') or self.objtype.startswith('attr')
200
201     def add_target_and_index(self, name_cls, sig, signode):
202         if self.objtype == 'global':
203             modname = ''
204         else:
205             modname = self.options.get(
206                 'module', self.env.temp_data.get('rb:module'))
207         separator = separators[self.objtype]
208         if self._is_class_member():
209             if signode['class']:
210                 prefix = modname and modname + '::' or ''
211             else:
212                 prefix = modname and modname + separator or ''
213         else:
214             prefix = modname and modname + separator or ''
215         fullname = prefix + name_cls[0]
216         # note target
217         if fullname not in self.state.document.ids:
218             signode['names'].append(fullname)
219             signode['ids'].append(fullname)
220             signode['first'] = (not self.names)
221             self.state.document.note_explicit_target(signode)
222             objects = self.env.domaindata['rb']['objects']
223             if fullname in objects:
224                 self.env.warn(
225                     self.env.docname,
226                     'duplicate object description of %s, ' % fullname +
227                     'other instance in ' +
228                     self.env.doc2path(objects[fullname][0]),
229                     self.lineno)
230             objects[fullname] = (self.env.docname, self.objtype)
231
232         indextext = self.get_index_text(modname, name_cls)
233         if indextext:
234             self.indexnode['entries'].append(
235                 _make_index('single', indextext, fullname, fullname))
236
237     def before_content(self):
238         # needed for automatic qualification of members (reset in subclasses)
239         self.clsname_set = False
240
241     def after_content(self):
242         if self.clsname_set:
243             self.env.temp_data['rb:class'] = None
244
245
246 class RubyModulelevel(RubyObject):
247     """
248     Description of an object on module level (functions, data).
249     """
250
251     def needs_arglist(self):
252         return self.objtype == 'function'
253
254     def get_index_text(self, modname, name_cls):
255         if self.objtype == 'function':
256             if not modname:
257                 return _('%s() (global function)') % name_cls[0]
258             return _('%s() (module function in %s)') % (name_cls[0], modname)
259         else:
260             return ''
261
262
263 class RubyGloballevel(RubyObject):
264     """
265     Description of an object on module level (functions, data).
266     """
267
268     def get_index_text(self, modname, name_cls):
269         if self.objtype == 'global':
270             return _('%s (global variable)') % name_cls[0]
271         else:
272             return ''
273
274
275 class RubyEverywhere(RubyObject):
276     """
277     Description of a class member (methods, attributes).
278     """
279
280     def needs_arglist(self):
281         return self.objtype == 'method'
282
283     def get_index_text(self, modname, name_cls):
284         name, cls = name_cls
285         add_modules = self.env.config.add_module_names
286         if self.objtype == 'method':
287             try:
288                 clsname, methname = ruby_rsplit(name)
289             except ValueError:
290                 if modname:
291                     return _('%s() (in module %s)') % (name, modname)
292                 else:
293                     return '%s()' % name
294             if modname and add_modules:
295                 return _('%s() (%s::%s method)') % (methname, modname,
296                                                           clsname)
297             else:
298                 return _('%s() (%s method)') % (methname, clsname)
299         else:
300             return ''
301
302
303 class RubyClasslike(RubyObject):
304     """
305     Description of a class-like object (classes, exceptions).
306     """
307
308     def get_signature_prefix(self, sig):
309         return self.objtype + ' '
310
311     def get_index_text(self, modname, name_cls):
312         if self.objtype == 'class':
313             if not modname:
314                 return _('%s (class)') % name_cls[0]
315             return _('%s (class in %s)') % (name_cls[0], modname)
316         elif self.objtype == 'exception':
317             return name_cls[0]
318         else:
319             return ''
320
321     def before_content(self):
322         RubyObject.before_content(self)
323         if self.names:
324             self.env.temp_data['rb:class'] = self.names[0][0]
325             self.clsname_set = True
326
327
328 class RubyClassmember(RubyObject):
329     """
330     Description of a class member (methods, attributes).
331     """
332
333     def needs_arglist(self):
334         return self.objtype.endswith('method')
335
336     def get_signature_prefix(self, sig):
337         if self.objtype == 'classmethod':
338             return "classmethod %s." % self.class_name
339         elif self.objtype == 'attr_reader':
340             return "attribute [R] "
341         elif self.objtype == 'attr_writer':
342             return "attribute [W] "
343         elif self.objtype == 'attr_accessor':
344             return "attribute [R/W] "
345         return ''
346
347     def get_index_text(self, modname, name_cls):
348         name, cls = name_cls
349         add_modules = self.env.config.add_module_names
350         if self.objtype == 'classmethod':
351             try:
352                 clsname, methname = ruby_rsplit(name)
353             except ValueError:
354                 return '%s()' % name
355             if modname:
356                 return _('%s() (%s.%s class method)') % (methname, modname,
357                                                          clsname)
358             else:
359                 return _('%s() (%s class method)') % (methname, clsname)
360         elif self.objtype.startswith('attr'):
361             try:
362                 clsname, attrname = ruby_rsplit(name)
363             except ValueError:
364                 return name
365             if modname and add_modules:
366                 return _('%s (%s.%s attribute)') % (attrname, modname, clsname)
367             else:
368                 return _('%s (%s attribute)') % (attrname, clsname)
369         else:
370             return ''
371
372     def before_content(self):
373         RubyObject.before_content(self)
374         lastname = self.names and self.names[-1][1]
375         if lastname and not self.env.temp_data.get('rb:class'):
376             self.env.temp_data['rb:class'] = lastname.strip('.')
377             self.clsname_set = True
378
379
380 class RubyModule(Directive):
381     """
382     Directive to mark description of a new module.
383     """
384
385     has_content = False
386     required_arguments = 1
387     optional_arguments = 0
388     final_argument_whitespace = False
389     option_spec = {
390         'platform': lambda x: x,
391         'synopsis': lambda x: x,
392         'noindex': directives.flag,
393         'deprecated': directives.flag,
394     }
395
396     def run(self):
397         env = self.state.document.settings.env
398         modname = self.arguments[0].strip()
399         noindex = 'noindex' in self.options
400         env.temp_data['rb:module'] = modname
401         env.domaindata['rb']['modules'][modname] = \
402             (env.docname, self.options.get('synopsis', ''),
403              self.options.get('platform', ''), 'deprecated' in self.options)
404         targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
405         self.state.document.note_explicit_target(targetnode)
406         ret = [targetnode]
407         # XXX this behavior of the module directive is a mess...
408         if 'platform' in self.options:
409             platform = self.options['platform']
410             node = nodes.paragraph()
411             node += nodes.emphasis('', _('Platforms: '))
412             node += nodes.Text(platform, platform)
413             ret.append(node)
414         # the synopsis isn't printed; in fact, it is only used in the
415         # modindex currently
416         if not noindex:
417             indextext = _('%s (module)') % modname
418             inode = addnodes.index(entries=[_make_index(
419                 'single', indextext, 'module-' + modname, modname)])
420             ret.append(inode)
421         return ret
422
423 def _make_index(entrytype, entryname, target, ignored, key=None):
424     # Sphinx 1.4 introduced backward incompatible changes, it now
425     # requires 5 tuples.  Last one is categorization key.  See
426     # http://www.sphinx-doc.org/en/stable/extdev/nodes.html#sphinx.addnodes.index
427     if version_info >= (1, 4, 0, '', 0):
428         return (entrytype, entryname, target, ignored, key)
429     else:
430         return (entrytype, entryname, target, ignored)
431
432 class RubyCurrentModule(Directive):
433     """
434     This directive is just to tell Sphinx that we're documenting
435     stuff in module foo, but links to module foo won't lead here.
436     """
437
438     has_content = False
439     required_arguments = 1
440     optional_arguments = 0
441     final_argument_whitespace = False
442     option_spec = {}
443
444     def run(self):
445         env = self.state.document.settings.env
446         modname = self.arguments[0].strip()
447         if modname == 'None':
448             env.temp_data['rb:module'] = None
449         else:
450             env.temp_data['rb:module'] = modname
451         return []
452
453
454 class RubyXRefRole(XRefRole):
455     def process_link(self, env, refnode, has_explicit_title, title, target):
456         if not has_explicit_title:
457             title = title.lstrip('.')   # only has a meaning for the target
458             title = title.lstrip('#')
459             if title.startswith("::"):
460                 title = title[2:]
461             target = target.lstrip('~') # only has a meaning for the title
462             # if the first character is a tilde, don't display the module/class
463             # parts of the contents
464             if title[0:1] == '~':
465                 m = re.search(r"(?:\.)?(?:#)?(?:::)?(.*)\Z", title)
466                 if m:
467                     title = m.group(1)
468         if not title.startswith("$"):
469             refnode['rb:module'] = env.temp_data.get('rb:module')
470             refnode['rb:class'] = env.temp_data.get('rb:class')
471         # if the first character is a dot, search more specific namespaces first
472         # else search builtins first
473         if target[0:1] == '.':
474             target = target[1:]
475             refnode['refspecific'] = True
476         return title, target
477
478
479 class RubyModuleIndex(Index):
480     """
481     Index subclass to provide the Ruby module index.
482     """
483
484     name = 'modindex'
485     localname = _('Ruby Module Index')
486     shortname = _('modules')
487
488     def generate(self, docnames=None):
489         content = {}
490         # list of prefixes to ignore
491         ignores = self.domain.env.config['modindex_common_prefix']
492         ignores = sorted(ignores, key=len, reverse=True)
493         # list of all modules, sorted by module name
494         modules = sorted(_iteritems(self.domain.data['modules']),
495                          key=lambda x: x[0].lower())
496         # sort out collapsable modules
497         prev_modname = ''
498         num_toplevels = 0
499         for modname, (docname, synopsis, platforms, deprecated) in modules:
500             if docnames and docname not in docnames:
501                 continue
502
503             for ignore in ignores:
504                 if modname.startswith(ignore):
505                     modname = modname[len(ignore):]
506                     stripped = ignore
507                     break
508             else:
509                 stripped = ''
510
511             # we stripped the whole module name?
512             if not modname:
513                 modname, stripped = stripped, ''
514
515             entries = content.setdefault(modname[0].lower(), [])
516
517             package = modname.split('::')[0]
518             if package != modname:
519                 # it's a submodule
520                 if prev_modname == package:
521                     # first submodule - make parent a group head
522                     entries[-1][1] = 1
523                 elif not prev_modname.startswith(package):
524                     # submodule without parent in list, add dummy entry
525                     entries.append([stripped + package, 1, '', '', '', '', ''])
526                 subtype = 2
527             else:
528                 num_toplevels += 1
529                 subtype = 0
530
531             qualifier = deprecated and _('Deprecated') or ''
532             entries.append([stripped + modname, subtype, docname,
533                             'module-' + stripped + modname, platforms,
534                             qualifier, synopsis])
535             prev_modname = modname
536
537         # apply heuristics when to collapse modindex at page load:
538         # only collapse if number of toplevel modules is larger than
539         # number of submodules
540         collapse = len(modules) - num_toplevels < num_toplevels
541
542         # sort by first letter
543         content = sorted(_iteritems(content))
544
545         return content, collapse
546
547
548 class RubyDomain(Domain):
549     """Ruby language domain."""
550     name = 'rb'
551     label = 'Ruby'
552     object_types = {
553         'function':        ObjType(_('function'),         'func', 'obj'),
554         'global':          ObjType(_('global variable'),  'global', 'obj'),
555         'method':          ObjType(_('method'),           'meth', 'obj'),
556         'class':           ObjType(_('class'),            'class', 'obj'),
557         'exception':       ObjType(_('exception'),        'exc', 'obj'),
558         'classmethod':     ObjType(_('class method'),     'meth', 'obj'),
559         'attr_reader':     ObjType(_('attribute'),        'attr', 'obj'),
560         'attr_writer':     ObjType(_('attribute'),        'attr', 'obj'),
561         'attr_accessor':   ObjType(_('attribute'),        'attr', 'obj'),
562         'const':           ObjType(_('const'),            'const', 'obj'),
563         'module':          ObjType(_('module'),           'mod', 'obj'),
564     }
565
566     directives = {
567         'function':        RubyModulelevel,
568         'global':          RubyGloballevel,
569         'method':          RubyEverywhere,
570         'const':           RubyEverywhere,
571         'class':           RubyClasslike,
572         'exception':       RubyClasslike,
573         'classmethod':     RubyClassmember,
574         'attr_reader':     RubyClassmember,
575         'attr_writer':     RubyClassmember,
576         'attr_accessor':   RubyClassmember,
577         'module':          RubyModule,
578         'currentmodule':   RubyCurrentModule,
579     }
580
581     roles = {
582         'func':  RubyXRefRole(fix_parens=False),
583         'global':RubyXRefRole(),
584         'class': RubyXRefRole(),
585         'exc':   RubyXRefRole(),
586         'meth':  RubyXRefRole(fix_parens=False),
587         'attr':  RubyXRefRole(),
588         'const': RubyXRefRole(),
589         'mod':   RubyXRefRole(),
590         'obj':   RubyXRefRole(),
591     }
592     initial_data = {
593         'objects': {},  # fullname -> docname, objtype
594         'modules': {},  # modname -> docname, synopsis, platform, deprecated
595     }
596     indices = [
597         RubyModuleIndex,
598     ]
599
600     def clear_doc(self, docname):
601         for fullname, (fn, _) in list(self.data['objects'].items()):
602             if fn == docname:
603                 del self.data['objects'][fullname]
604         for modname, (fn, _, _, _) in list(self.data['modules'].items()):
605             if fn == docname:
606                 del self.data['modules'][modname]
607
608     def find_obj(self, env, modname, classname, name, type, searchorder=0):
609         """
610         Find a Ruby object for "name", perhaps using the given module and/or
611         classname.
612         """
613         # skip parens
614         if name[-2:] == '()':
615             name = name[:-2]
616
617         if not name:
618             return None, None
619
620         objects = self.data['objects']
621
622         newname = None
623         if searchorder == 1:
624             if modname and classname and \
625                      modname + '::' + classname + '#' + name in objects:
626                 newname = modname + '::' + classname + '#' + name
627             elif modname and classname and \
628                      modname + '::' + classname + '.' + name in objects:
629                 newname = modname + '::' + classname + '.' + name
630             elif modname and modname + '::' + name in objects:
631                 newname = modname + '::' + name
632             elif modname and modname + '#' + name in objects:
633                 newname = modname + '#' + name
634             elif modname and modname + '.' + name in objects:
635                 newname = modname + '.' + name
636             elif classname and classname + '.' + name in objects:
637                 newname = classname + '.' + name
638             elif classname and classname + '#' + name in objects:
639                 newname = classname + '#' + name
640             elif name in objects:
641                 newname = name
642         else:
643             if name in objects:
644                 newname = name
645             elif classname and classname + '.' + name in objects:
646                 newname = classname + '.' + name
647             elif classname and classname + '#' + name in objects:
648                 newname = classname + '#' + name
649             elif modname and modname + '::' + name in objects:
650                 newname = modname + '::' + name
651             elif modname and modname + '#' + name in objects:
652                 newname = modname + '#' + name
653             elif modname and modname + '.' + name in objects:
654                 newname = modname + '.' + name
655             elif modname and classname and \
656                      modname + '::' + classname + '#' + name in objects:
657                 newname = modname + '::' + classname + '#' + name
658             elif modname and classname and \
659                      modname + '::' + classname + '.' + name in objects:
660                 newname = modname + '::' + classname + '.' + name
661             # special case: object methods
662             elif type in ('func', 'meth') and '.' not in name and \
663                  'object.' + name in objects:
664                 newname = 'object.' + name
665         if newname is None:
666             return None, None
667         return newname, objects[newname]
668
669     def resolve_xref(self, env, fromdocname, builder,
670                      typ, target, node, contnode):
671         if (typ == 'mod' or
672             typ == 'obj' and target in self.data['modules']):
673             docname, synopsis, platform, deprecated = \
674                 self.data['modules'].get(target, ('','','', ''))
675             if not docname:
676                 return None
677             else:
678                 title = '%s%s%s' % ((platform and '(%s) ' % platform),
679                                     synopsis,
680                                     (deprecated and ' (deprecated)' or ''))
681                 return make_refnode(builder, fromdocname, docname,
682                                     'module-' + target, contnode, title)
683         else:
684             modname = node.get('rb:module')
685             clsname = node.get('rb:class')
686             searchorder = node.hasattr('refspecific') and 1 or 0
687             name, obj = self.find_obj(env, modname, clsname,
688                                       target, typ, searchorder)
689             if not obj:
690                 return None
691             else:
692                 return make_refnode(builder, fromdocname, obj[0], name,
693                                     contnode, name)
694
695     def get_objects(self):
696         for modname, info in _iteritems(self.data['modules']):
697             yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
698         for refname, (docname, type) in _iteritems(self.data['objects']):
699             yield (refname, refname, type, docname, refname, 1)
700
701
702 def setup(app):
703     app.add_domain(RubyDomain)