# -*- coding: utf-8 -*-
"""
A fortran domain for sphinx
"""
# Copyright or © or Copr. Actimar/IFREMER (2010-2015)
#
# This software is a computer program whose purpose is to provide
# utilities for handling oceanographic and atmospheric data,
# with the ultimate goal of validating the MARS model from IFREMER.
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
#
import re
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from sphinx import addnodes
from sphinx.roles import XRefRole
from sphinx.locale import l_, _
from sphinx.domains import Domain, ObjType, Index
from sphinx.directives import ObjectDescription
from sphinx.util.nodes import make_refnode
from sphinx.util.compat import Directive
from sphinx.util.docfields import Field, GroupedField, TypedField, DocFieldTransformer, _is_single_paragraph
# FIXME: surlignage en jaune de la recherche inactive si "/" dans target
# Utilities
[docs]def convert_arithm(node, expr, modname=None, nodefmt=nodes.Text):
"""Format an arithmetic expression for a node"""
ops = re.findall(r'(\W+)',expr)
nums = re.split(r'\W+', expr)
if len(nums)!=len(ops): ops.append('')
for num, op in zip(nums, ops):
if num:
if num[0].isalpha():
refnode = addnodes.pending_xref(
'', refdomain='f', reftype='var', reftarget=num,
modname=modname)
refnode += nodefmt(num, num)
node += refnode
else:
node += nodefmt(num, num)
if op:
op = op.replace(':', '*')
node += nodefmt(op, op)
[docs]def parse_shape(shape):
if not shape: return
if not shape.startswith('('): shape = '('+shape
if not shape.endswith(')'): shape += ')'
return shape
[docs]def add_shape(node, shape, modname=None, nodefmt=nodes.Text):
"""Format a shape expression for a node"""
dims = re.split('\s*,\s*', shape.strip('( )'))
node += nodefmt(' (',' (')
convert_arithm(node, shape.strip('( )'), modname=modname, nodefmt=nodefmt)
node += nodefmt(')',')')
#class fortranfield(nodes.Admonition, nodes.TextElement): pass
# Doc fields
re_name_shape = re.compile('(\w+)(\(.+\))?')
re_fieldname_match = re.compile(r'(?P<type>\b\w+\b)?\s*(?P<name>\b\w+\b)\s*(?P<shape>\(.*\))?\s*(?P<sattrs>\[.+\])?').match
class FortranField(Field):
def make_xref(self, rolename, domain, target, innernode=nodes.emphasis,
modname=None, typename=None):
if not rolename:
return innernode(target, target)
refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False,
reftype=rolename, reftarget=target,
modname=modname, typename=typename)
refnode += innernode(target, target)
return refnode
[docs]class FortranCallField(FortranField):
is_grouped = True
def __init__(self, name, names=(), label=None, rolename=None):
Field.__init__(self, name, names, label, True, rolename)
[docs] def make_field(self, types, domain, items, **kwargs):
fieldname = nodes.field_name('', self.label)
#par = Field.make_field(self, types, domain, items[0])
par = nodes.paragraph()
for i,item in enumerate(items):
if i: par += nodes.Text(' ')
par += item[1]#Field.make_field(self, types, domain, item)
fieldbody = nodes.field_body('', par)
return nodes.field('', fieldname, fieldbody)
[docs]class FortranCompleteField(FortranField, GroupedField):
"""
A doc field that is grouped and has type information for the arguments. It
always has an argument. The argument can be linked using the given
*rolename*, the type using the given *typerolename*.
Two uses are possible: either parameter and type description are given
separately, using a field from *names* and one from *typenames*,
respectively, or both are given using a field from *names*, see the example.
Example::
:param foo: description of parameter foo
:type foo: SomeClass
-- or --
:param SomeClass foo: description of parameter foo
"""
is_typed = 2
def __init__(self, name, names=(), typenames=(), label=None,
rolename=None, typerolename=None,
shapenames=None, attrnames=None,
prefix=None,
strong=True,
can_collapse=False):
GroupedField.__init__(self, name, names, label, rolename, can_collapse)
self.typenames = typenames
self.typerolename = typerolename
self.shapenames = shapenames
self.attrnames = attrnames
self.prefix = prefix
if strong:
self.namefmt = nodes.strong
else:
self.namefmt = addnodes.desc_name
[docs] def make_field(self, types, domain, items, shapes=None, attrs=None,
modname=None, typename=None):
def handle_item(fieldarg, content):
par = nodes.paragraph()
if self.prefix:
par += self.namefmt(self.prefix, self.prefix)
par += self.make_xref(self.rolename, domain, fieldarg, self.namefmt,
modname=modname, typename=typename)
#par += self.namefmt(fieldarg, fieldarg)
fieldtype = types.pop(fieldarg, None)
fieldshape = shapes and shapes.pop(fieldarg, None)
fieldattrs = attrs and attrs.pop(fieldarg, None)
if fieldshape:
shape = parse_shape(fieldshape[0].astext())
#par += nodes.Text(' %s'%shape)
add_shape(par, shape, modname=modname)
if fieldtype or fieldattrs:
par += nodes.emphasis(' [',' [' )
if fieldtype:
if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text):
thistypename = fieldtype[0].astext()
#typename = u''.join(n.astext() for n in fieldtype)
par += self.make_xref(self.typerolename, domain, thistypename,
modname=modname, typename=typename)
else:
par += fieldtype
if fieldattrs:
if fieldtype: par += nodes.emphasis(',',',')
par += fieldattrs
if fieldtype or fieldattrs:
par += nodes.emphasis(']',']')
if content:
par += nodes.Text(' :: ')
par += content
return par
if len(items) == 1 and self.can_collapse:
fieldarg, content = items[0]
bodynode = handle_item(fieldarg, content)
else:
bodynode = self.list_type()
for fieldarg, content in items:
bodynode += nodes.list_item('', handle_item(fieldarg, content))
label = self.label or ''
fieldname = nodes.field_name('', label)
fieldbody = nodes.field_body('', bodynode)
return nodes.field('', fieldname, fieldbody)
f_sep = '/'
f_sig_re = re.compile(
r'''^ (\w+(?:[^%%%(f_sep)s]%(f_sep)s\w+))? \s* # type
(\b(?:subroutine|function))? \s* # objtype
(\b\w+%(f_sep)s)? # module name
(\b\w+%%)? # type name
(\b\w+) \s* # thing name
(?: \((.*)\))? # optional: arguments
$ # and nothing more
'''%dict(f_sep=f_sep), re.VERBOSE+re.I)
# Directives
# RE to split at word boundaries
wsplit_re = re.compile(r'(\W+)')
f_type_re = re.compile('^([\w]+).*$')
f_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ','
#def fortran_rsplit(fullname):
# items = [item for item in f_separator.findall(fullname)]
# return ''.join(items[:-2]), items[-1]
[docs]def _pseudo_parse_arglist(signode, arglist):
""""Parse" a list of arguments separated by commas.
Arguments can have "optional" annotations given by enclosing them in
brackets. Currently, this will split at any comma, even if it's inside a
string literal (e.g. default argument value).
"""
paramlist = addnodes.desc_parameterlist()
stack = [paramlist]
try:
for argument in arglist.split(','):
argument = argument.strip()
ends_open = ends_close = 0
while argument.startswith('['):
stack.append(addnodes.desc_optional())
stack[-2] += stack[-1]
argument = argument[1:].strip()
while argument.startswith(']'):
stack.pop()
argument = argument[1:].strip()
while argument.endswith(']'):
ends_close += 1
argument = argument[:-1].strip()
while argument.endswith('['):
ends_open += 1
argument = argument[:-1].strip()
if argument:
stack[-1] += addnodes.desc_parameter(argument, argument)
while ends_open:
stack.append(addnodes.desc_optional())
stack[-2] += stack[-1]
ends_open -= 1
while ends_close:
stack.pop()
ends_close -= 1
if len(stack) != 1:
raise IndexError
except IndexError:
# if there are too few or too many elements on the stack, just give up
# and treat the whole argument list as one argument, discarding the
# already partially populated paramlist node
signode += addnodes.desc_parameterlist()
signode[-1] += addnodes.desc_parameter(arglist, arglist)
else:
signode += paramlist
[docs]class FortranObject(ObjectDescription):
"""
Description of a general Fortran object.
"""
option_spec = {
'noindex': directives.flag,
'module': directives.unchanged,
'type': directives.unchanged,
'shape': parse_shape,
'attrs':directives.unchanged,
}
doc_field_types = [
FortranCompleteField('parameter', label=l_('Parameters'),
names=('p', 'param', 'parameter', 'a', 'arg', 'argument'),
#rolename='var',
typerolename='type',
typenames=('paramtype', 'type', 'ptype'),
shapenames=('shape', 'pshape'),
attrnames=('attrs', 'pattrs', 'attr'),
can_collapse=True),
FortranCompleteField('optional', label=l_('Options'),
names=('o', 'optional', 'opt', 'keyword', 'option'),
#rolename='var',
typerolename='type',
typenames=('optparamtype', 'otype'),
shapenames=('oshape',),
attrnames=('oattrs', 'oattr'),
can_collapse=True),
FortranCompleteField('typefield', label=l_('Type fields'),
names=('f', 'field', 'typef', 'typefield'),
#rolename='typef',
typerolename='type',
typenames=('fieldtype', 'ftype'),
shapenames=('fshape',),
attrnames=('fattrs', 'fattr'),
prefix='% ',
strong=False,
can_collapse=False),
FortranCompleteField('return', label=l_('Return'),
names=('r', 'return', 'returns'),
typerolename='type',
typenames=('returntype', 'rtype'),
shapenames=('rshape',),
attrnames=('rattrs', 'rattr'),
can_collapse=True),
FortranCallField('calledfrom', label=l_('Called from'),
names=('calledfrom', 'from')),
FortranCallField('callto', label=l_('Call to'),
names=('callto', 'to')),
]
# These Fortran types aren't described anywhere, so don't try to create
# a cross-reference to them
stopwords = set(('float', 'integer', 'character', 'double', 'long'))
_parens = ''
# def _parse_type(self, node, ftype):
# m = f_type_re.match(ftype)
# tnode = nodes.Text(ftype, ftype)
# modname = self.options.get(
# 'module', self.env.temp_data.get('f:module'))
# if m :
# ftype = m.groups(0)
# if ftype not in self.stopwords:
# pnode = addnodes.pending_xref(
# '', refdomain='f', reftype='type', reftarget=ftype,
# modname=modname)
# pnode += tnode
# node += pnode
# else:
# node += tnode
# else:
# node += tnode
[docs] def get_signature_prefix(self, sig):
"""
May return a prefix to put before the object name in the signature.
"""
return ''
[docs] def needs_arglist(self):
"""
May return true if an empty argument list is to be generated even if
the document contains none.
"""
return False
[docs] def handle_signature(self, sig, signode):
"""
Transform a Fortran signature into RST nodes.
Returns (fully qualified name of the thing, classname if any).
If inside a class, the current class name is handled intelligently:
* it is stripped from the displayed name if present
* it is added to the full name (return value) if not present
"""
m = f_sig_re.match(sig)
if m is None:
raise ValueError
ftype, objtype, modname, typename, name, arglist = m.groups()
if not typename: typename = ""
# determine module, type, shape and attributes
modname = (modname and modname[:-1]) or self.options.get(
'module', self.env.temp_data.get('f:module'))
if typename:
name = typename[:-1]
attrs = self.options.get('attrs')
shape = parse_shape(self.options.get('shape'))
ftype = ftype or self.options.get('type')
if self.objtype=='typefield' and not typename:
raise ValueError
#if typename: name = typename+'%'+name
#fullname = name
#if modname:
if self.objtype=='program':
fullname = name
else:
fullname = (modname or '_') + f_sep + name
signode['module'] = modname
signode['type'] = typename
signode['fullname'] = fullname
# Add "function" or "subroutine" tag
sig_prefix = self.get_signature_prefix(sig)
if objtype or sig_prefix:
objtype = objtype or sig_prefix
signode += addnodes.desc_annotation(objtype+' ', objtype+' ')
# Add module
if self.env.config.add_module_names and modname and self.objtype!='typefield':
nodetext = modname + f_sep
signode += addnodes.desc_addname(nodetext, nodetext)
# Add name
signode += addnodes.desc_name(name, name)
# In the parenthesis
if self.needs_arglist(): # call for functions and subroutines
if arglist: # Calling arguments
_pseudo_parse_arglist(signode, arglist)
elif self.needs_arglist(): # for callables, add an empty parameter list
signode += addnodes.desc_parameterlist()
elif arglist and not shape: # Declare shape instead of arguments (variables)
shape = arglist
# Add remaining
self.add_shape_and_attrs(signode, modname, ftype, shape, attrs)
return fullname, ftype
[docs] def add_shape_and_attrs(self, signode, modname, ftype, shape, attrs):
# add shape
if shape:
add_shape(signode, shape, modname=modname)
#signode += nodes.Text(' '+shape)
# add type ('float', 'interger', etc)
if ftype or attrs:
signode += nodes.emphasis(' [', ' [')
if ftype:
refnode = addnodes.pending_xref(
'', refdomain='f', reftype='type', reftarget=ftype,
modname=modname,)
refnode += nodes.emphasis(ftype, ftype)
signode += refnode
#tnode = addnodes.desc_type(ftype, ftype)
#tnode +=
#signode += addnodes.desc_type(ftype, ftype)
#signode +=
# signode += addnodes.desc_type('', '')
# self._parse_type(signode[-1], ftype)
if attrs:
if ftype: signode += nodes.emphasis(',',',')
for iatt,att in enumerate(re.split('\s*,\s*', attrs)):
if iatt:
signode += nodes.emphasis(',',',')
if att.startswith('parameter'):
value = att.split('=')[1]
signode += nodes.emphasis('parameter=', 'parameter=')
convert_arithm(signode, value, modname=modname)
else:
signode += nodes.emphasis(att,att)
#signode += nodes.emphasis(attrs, attrs)
if ftype or attrs:
signode += nodes.emphasis(']', ']')
[docs] def add_target_and_index(self, name, sig, signode):
#modname = self.options.get(
#'module', self.env.temp_data.get('f:module'))
modname = signode.get(
'module', self.env.temp_data.get('f:module'))
# fullname = (modname and modname + '/' or '') + name[0]
fullname = 'f' + f_sep + name[0]
# note target
if fullname not in self.state.document.ids:
signode['names'].append(fullname)
signode['ids'].append(fullname)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
objects = self.env.domaindata['f']['objects']
if fullname in objects:
self.env.warn(
self.env.docname,
'duplicate object description of %s, ' % fullname +
'other instance in ' +
self.env.doc2path(objects[fullname][0]),
self.lineno)
objects[fullname] = (self.env.docname, self.objtype)
indextext = self.get_index_text(modname, fullname)
if indextext:
self.indexnode['entries'].append(('single', indextext,
fullname, fullname))
[docs] def before_content(self):
# needed for automatic qualification of fields (reset in subclasses)
self.typename_set = False
[docs] def after_content(self):
if self.typename_set:
self.env.temp_data['f:type'] = None
[docs] def get_index_text(self, modname, name):
add_modules = self.env.config.add_module_names
if name.startswith('f'+f_sep): name = name[2:]
mn = modname or '_'
sobj = ''
if name.startswith(mn+f_sep):
name = name[len(mn)+1:]
if self.objtype=='type':
sobj = _('fortran type')
if self.objtype=='typefield':
sobj = _('fortran type field')
elif self.objtype=='variable':
sobj = _('fortran variable')
elif self.objtype=='subroutine':
sobj = _('fortran subroutine')
elif self.objtype=='function':
sobj = _('fortran function')
elif self.objtype=='module':
sobj = _('fortran module')
modname = ''
elif self.objtype=='program':
sobj = _('fortran program')
modname = ''
sinmodule = (_(' in module %s')%modname) if modname and add_modules else ''
return '%s%s (%s%s)'%(name, self._parens, sobj, sinmodule)
[docs]class FortranSpecial:
[docs] def get_signature_prefix(self, sig):
"""
May return a prefix to put before the object name in the signature.
"""
return self.objtype+' '
[docs]class FortranType(FortranSpecial, WithFortranDocFieldTransformer, FortranObject):
[docs] def before_content(self):
FortranObject.before_content(self)
if self.names:
self.env.temp_data['f:type'] = self.names[0][0].split(f_sep)[-1]
self.typename_set = True
[docs]class FortranTypeField(FortranObject):
#def handle_signature(self, sig, signode):
#"""
#Transform a Fortran signature into RST nodes.
#Returns (fully qualified name of the thing, classname if any).
#If inside a class, the current class name is handled intelligently:
#* it is stripped from the displayed name if present
#* it is added to the full name (return value) if not present
#"""
#m = f_sig_re.match(sig)
#if m is None:
#raise ValueError
#ftype, objtype, modname, typename, name, arglist = m.groups()
#print 'handle_signature', ftype, objtype, modname, typename, name, arglist
#if not typename: typename = ""
## determine module and type
#modname = (modname and modname[:-1]) or self.options.get(
#'module', self.env.temp_data.get('f:module'))
#typename = (typename and typename[:-1]) or self.options.get(
#'type', self.env.temp_data.get('f:type'))
##print ' mod type', modname, typename
##print self.objtype
#if self.objtype=='typefield' and not typename:
#raise ValueError
#if typename: name = typename+'%'+name
#fullname = name
#if modname:
#fullname = modname + f_sep + name
#signode['module'] = modname
#signode['type'] = typename
#signode['fullname'] = fullname
## Fill node
#signode += addnodes.desc_name(name, name)
#shape = self.options.get('shape')
#if shape: signode += nodes.Text(shape, shape)
#ftype = self.options.get('type', ftype)
#attr= self.options.get('attr')
#if ftype or attr:
#signode += nodes.Text(' :: ', ' :: ')
#if ftype: signode += nodes.emphasis('', ftype)
#if attr: signode += nodes.literal('', '['+attr+']')
#if self.content:
#signode += nodes.Text(': ', ': ')
#argnodes, msgs = self.state.inline_text(' '.join(self.content), self.lineno)
#signode += argnodes
#signode += msgs
#return fullname, ftype
[docs] def before_content(self):
FortranObject.before_content(self)
lastname = self.names and self.names[-1][1]
if lastname and not self.env.temp_data.get('f:type'):
self.env.temp_data['f:type'] = lastname.split(f_sep)[-1]
self.typename_set = True
[docs]class FortranProgram(FortranSpecial, WithFortranDocFieldTransformer, FortranObject):
pass
[docs]class FortranWithSig(FortranSpecial, WithFortranDocFieldTransformer, FortranObject):
"""
Description of a function of subroutine
"""
_parens = '()'
[docs] def needs_arglist(self):
return True
[docs] def get_signature_prefix(self, sig):
"""
May return a prefix to put before the object name in the signature.
"""
return self.objtype+' '
[docs]class FortranField(Directive):
"""
Directive to describe a change/addition/deprecation in a specific version.
"""
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {
'type': directives.unchanged,
'shape': parse_shape,
'attrs': directives.unchanged,
}
[docs] def run(self):
from docutils import nodes
node = nodes.paragraph()
node += addnodes.desc_name(self.arguments[0], self.arguments[0])
shape = self.options.get('shape')
if shape :
#node += nodes.Text(shape, shape)
add_shape(node, shape)
type = self.options.get('type')
attrs= self.options.get('attrs')
if type or attrs:
node += nodes.Text(' :: ', ' :: ')
if type: node += nodes.emphasis('', type)
if attr: node += nodes.literal('', '['+attr+']')
if self.content:
node += nodes.Text(': ', ': ')
argnodes, msgs = self.state.inline_text(' '.join(self.content), self.lineno)
node += argnodes
node += msgs
ret = [node]
# env = self.state.document.settings.env
# env.note_versionchange(node['type'], node['version'], node, self.lineno)
return ret
[docs]class FortranModule(Directive):
"""
Directive to mark description of a new module.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'platform': lambda x: x,
'synopsis': lambda x: x,
'noindex': directives.flag,
'deprecated': directives.flag,
}
[docs] def run(self):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
noindex = 'noindex' in self.options
env.temp_data['f:module'] = modname
env.domaindata['f']['modules'][modname] = \
(env.docname, self.options.get('synopsis', ''),
self.options.get('platform', ''), 'deprecated' in self.options)
env.domaindata['f']['objects']['f'+f_sep+modname] = (env.docname, 'module')
#targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
targetnode = nodes.target('', '', ids=['f'+f_sep+modname], ismod=True)
self.state.document.note_explicit_target(targetnode)
ret = [targetnode]
# XXX this behavior of the module directive is a mess...
if 'platform' in self.options:
platform = self.options['platform']
node = nodes.paragraph()
node += nodes.emphasis('', _('Platforms: '))
node += nodes.Text(platform, platform)
ret.append(node)
# the synopsis isn't printed; in fact, it is only used in the
# modindex currently
if not noindex:
indextext = _('%s (module)') % modname
#inode = addnodes.index(entries=[('single', indextext,
#'module-' + modname, modname)])
inode = addnodes.index(entries=[('single', indextext,
'f' + f_sep + modname, modname)])
ret.append(inode)
return ret
[docs]class FortranCurrentModule(Directive):
"""
This directive is just to tell Sphinx that we're documenting
stuff in module foo, but links to module foo won't lead here.
"""
has_content = False
required_arguments = 0
optional_arguments = 1
final_argument_whitespace = False
option_spec = {}
[docs] def run(self):
env = self.state.document.settings.env
modname = self.arguments and (self.arguments[0] or self.arguments[0].strip()) or None
if modname:
env.temp_data['f:module'] = None
else:
env.temp_data['f:module'] = modname
return []
[docs]class FortranXRefRole(XRefRole):
[docs] def process_link(self, env, refnode, has_explicit_title, title, target):
refnode['f:module'] = env.temp_data.get('f:module')
refnode['f:type'] = env.temp_data.get('f:type')
if not has_explicit_title:
title = title.lstrip('.') # only has a meaning for the target
target = target.lstrip('~') # only has a meaning for the title
# if the first character is a tilde, don't display the module/class
# parts of the contents
if title[0:1] == '~':
title = title[1:].split(f_sep)[-1]
#elif '%' not in title and refnode['f:type']:
#title = '%s%%%s'%(refnode['f:type'], title)
# if the first character is a sep, search more specific namespaces first
# else search builtins first
if target.startswith(f_sep):
target = target[1:]
refnode['refspecific'] = True
return title, target
[docs]class FortranModuleIndex(Index):
"""
Index subclass to provide the Fortran module index.
"""
name = 'modindex'
localname = l_('Fortran Module Index')
shortname = l_('fortran modules')
[docs] def generate(self, docnames=None):
content = {}
# list of prefixes to ignore
ignores = self.domain.env.config['modindex_common_prefix']
ignores = sorted(ignores, key=len, reverse=True)
# list of all modules, sorted by module name
modules = sorted(self.domain.data['modules'].iteritems(),
key=lambda x: x[0].lower())
# sort out collapsable modules
prev_modname = ''
num_toplevels = 0
for modname, (docname, synopsis, platforms, deprecated) in modules:
if docnames and docname not in docnames:
continue
for ignore in ignores:
if modname.startswith(ignore):
modname = modname[len(ignore):]
stripped = ignore
break
else:
stripped = ''
# we stripped the whole module name?
if not modname:
modname, stripped = stripped, ''
entries = content.setdefault(modname[0].lower(), [])
package = modname.split(f_sep)[0]
if package != modname:
# it's a submodule
if prev_modname == package:
# first submodule - make parent a group head
entries[-1][1] = 1
elif not prev_modname.startswith(package):
# submodule without parent in list, add dummy entry
entries.append([stripped + package, 1, '', '', '', '', ''])
subtype = 2
else:
num_toplevels += 1
subtype = 0
qualifier = deprecated and _('Deprecated') or ''
#entries.append([stripped + modname, subtype, docname,
#'module-' + stripped + modname, platforms,
#qualifier, synopsis])
entries.append([stripped + modname, subtype, docname,
'f' + f_sep + stripped + modname, platforms,
qualifier, synopsis or ''])
prev_modname = modname
# apply heuristics when to collapse modindex at page load:
# only collapse if number of toplevel modules is larger than
# number of submodules
collapse = len(modules) - num_toplevels < num_toplevels
# sort by first letter
content = sorted(content.iteritems())
return content, collapse
[docs]class FortranDomain(Domain):
"""Fortran language domain."""
name = 'f'
label = 'Fortran'
object_types = {
'program': ObjType(l_('program'), 'prog'),
'type': ObjType(l_('type'), 'type'),
'variable': ObjType(l_('variable'), 'var'),
'function': ObjType(l_('function'), 'func'),
'subroutine': ObjType(l_('subroutine'), 'func', 'subr'),
'module': ObjType(l_('module'), 'mod'),
}
directives = {
'program': FortranProgram,
'type': FortranType,
'variable': FortranObject,
'function': FortranWithSig,
'subroutine': FortranWithSig,
'module': FortranModule,
'currentmodule': FortranCurrentModule,
}
roles = {
'prog': FortranXRefRole(),
'type': FortranXRefRole(),
'var':FortranXRefRole(),
'func': FortranXRefRole(fix_parens=True),
'subr': FortranXRefRole(fix_parens=True),
'mod': FortranXRefRole(),
}
initial_data = {
'objects': {}, # fullname -> docname, objtype
'modules': {}, # modname -> docname, synopsis, platform, deprecated
}
indices = [
FortranModuleIndex,
]
[docs] def clear_doc(self, docname):
for fullname, (fn, _) in self.data['objects'].items():
if fn == docname:
del self.data['objects'][fullname]
for modname, (fn, _, _, _) in self.data['modules'].items():
if fn == docname:
del self.data['modules'][modname]
[docs] def find_obj(self, env, modname, name, role, searchorder=0):
"""
Find a Fortran object for "name", perhaps using the given module and/or
typename.
:Params:
- **searchorder**, optional: Start using relative search
"""
# skip parens
if name.endswith('()'):
name = name[:-2]
if not name:
return None, None
if f_sep in name:
modname, name = name.split(f_sep)
#modname = modname or '_'
if '%' in name:
name, tmp = name.split('%')
objects = self.data['objects']
newname = None
matches = []
objtypes = self.objtypes_for_role(role)
if searchorder == 1: # :role:`/toto`
if role in ['mod', 'prog']:
if 'f'+f_sep + name not in objects: # exact match
return []
newname = 'f'+f_sep + name
elif modname and 'f'+f_sep + modname + f_sep + name in objects and \
objects['f'+f_sep + modname + f_sep + name][1] in objtypes:
newname = 'f'+f_sep + modname + f_sep + name
elif 'f'+f_sep + '_' + f_sep + name in objects and \
objects['f'+f_sep + '_' + f_sep + name][1] in objtypes:
newname = 'f'+f_sep + '_' + f_sep + name
elif 'f'+f_sep + name in objects and \
objects['f'+f_sep + name][1] in objtypes:
newname = 'f'+f_sep + name
elif name in objects and \
objects[name][1] in objtypes:
newname = name
else: # :role:`toto`
# NOTE: searching for exact match, object type is not considered
if 'f'+f_sep + name in objects:
newname = 'f'+f_sep + name
elif role in ['mod', 'prog']:
# only exact matches allowed for modules
return []
elif 'f'+f_sep + '_' + f_sep + name in objects:
newname = 'f' + f_sep + '_' + f_sep +name
elif modname and 'f'+f_sep + modname + f_sep + name in objects:
newname = 'f' + f_sep + modname + f_sep +name
# Last chance: fuzzy search
if newname is None:
matches = [(oname, objects[oname]) for oname in objects
if oname.endswith(f_sep+name)
and objects[oname][1] in objtypes]
else:
matches.append((newname, objects[newname]))
return matches
[docs] def resolve_xref(self, env, fromdocname, builder,
type, target, node, contnode):
modname = node.get('f:module', node.get('modname'))
typename = node.get('f:type', node.get('typename'))
searchorder = node.hasattr('refspecific') and 1 or 0
matches = self.find_obj(env, modname, target, type, searchorder)
if not matches:
return None
elif len(matches) > 1:
env.warn(fromdocname,
'more than one target found for cross-reference '
'%r: %s' % (target,
', '.join(match[0] for match in matches)),
node.line)
name, obj = matches[0]
if obj[1] == 'module':
# get additional info for modules
docname, synopsis, platform, deprecated = self.data['modules'][name[1+len(f_sep):]]
assert docname == obj[0]
title = name
if synopsis:
title += ': ' + synopsis
if deprecated:
title += _(' (deprecated)')
#return make_refnode(builder, fromdocname, docname,
#'module-' + name, contnode, title)
return make_refnode(builder, fromdocname, docname,
name, contnode, title)
else:
return make_refnode(builder, fromdocname, obj[0], name,
contnode, name)
[docs] def get_objects(self):
for modname, info in self.data['modules'].iteritems():
yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
for refname, (docname, type) in self.data['objects'].iteritems():
yield (refname, refname, type, docname, refname, 1)
[docs]def setup(app):
app.add_domain(FortranDomain)