Commit 55b54c51 authored by Leonid Onokhov's avatar Leonid Onokhov

Docs: added reference index

Reference index shows fields and flags for project.cabal and fields for
pkgname.cabal

[ci skip]
parent 24f6b2a7
...@@ -3,5 +3,6 @@ ...@@ -3,5 +3,6 @@
{% block menu %} {% block menu %}
{{ super() }} {{ super() }}
<a href="genindex.html">Index</a> <a href="genindex.html">Index</a>
<a href="cabal-projectindex.html">Reference</a>
{% endblock %} {% endblock %}
...@@ -11,7 +11,7 @@ from distutils.version import StrictVersion ...@@ -11,7 +11,7 @@ from distutils.version import StrictVersion
from sphinx import addnodes from sphinx import addnodes
from sphinx.directives import ObjectDescription from sphinx.directives import ObjectDescription
from sphinx.domains import ObjType, Domain from sphinx.domains import ObjType, Domain, Index
from sphinx.domains.std import StandardDomain from sphinx.domains.std import StandardDomain
from sphinx.locale import l_, _ from sphinx.locale import l_, _
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
...@@ -55,71 +55,158 @@ class Meta(object): ...@@ -55,71 +55,158 @@ class Meta(object):
''' '''
Meta data associated with object Meta data associated with object
''' '''
def __init__(self, since=None, deprecated=None): def __init__(self, since=None, deprecated=None, synopsis=None, title=None, index=0):
self.since = since self.since = since
self.deprecated = deprecated self.deprecated = deprecated
self.synopsis = synopsis
self.title = title
self.index = index
class CabalPackageSection(Directive): def find_section_title(parent):
'''
Find current section title if possible
'''
while parent is not None:
if isinstance(parent, nodes.section):
break
parent = parent.parent
if parent is None:
return None
for kid in parent:
if isinstance(kid, nodes.title):
return kid.astext()
return None
class CabalSection(Directive):
""" """
Directive marks a package.cabal section described next Marks section to which following objects belong.
and adds it to index. Can be referenced with pkg-section-name. Does not generate any output besides anchor
""" """
has_content = False has_content = False
required_arguments = 1 required_arguments = 1
optional_arguments = 0 optional_arguments = 0
final_argument_whitespace = True final_argument_whitespace = True
option_spec = { option_spec = {
'name': lambda x: x,
'deprecated': parse_deprecated, 'deprecated': parse_deprecated,
'since' : StrictVersion 'since' : StrictVersion,
'synopsis' : lambda x:x,
'index' : int
} }
section_key = 'cabal:pkg-section'
target_prefix = 'pkg-section-'
indextemplate = ''
indextype = 'pair'
def get_index_entry(self, name):
return self.indextemplate % name
def run(self): def run(self):
env = self.state.document.settings.env env = self.state.document.settings.env
section = self.arguments[0].strip() section = self.arguments[0].strip()
if ':' in self.name:
self.domain, self.objtype = self.name.split(':', 1)
else:
self.domain, self.objtype = '', self.name
if section == 'None': if section == 'None':
env.ref_context.pop('cabal:section', None) env.ref_context.pop(self.section_key, None)
return [] return []
env.ref_context['cabal:section'] = section env.ref_context[self.section_key] = section
targetname = 'pkg-section-' + section targetname = self.target_prefix + section
node = nodes.target('', '', ids=[targetname]) node = nodes.target('', '', ids=[targetname])
self.state.document.note_explicit_target(node) self.state.document.note_explicit_target(node)
indexentry = section + '; package.cabal section'
inode = addnodes.index(entries=[('pair', indexentry, indexentry = self.get_index_entry(section)
targetname, '', None)])
inode = addnodes.index(
entries = [
(self.indextype, indexentry, targetname, '', None)])
# find title of parent section node
title = find_section_title(self.state.parent)
data_key = CabalDomain.types[self.objtype]
index_key = data_key + '-num'
# find how many sections in this document were added
num = env.domaindata['cabal'][index_key].get(env.docname, 0)
env.domaindata['cabal'][index_key][env.docname] = num + 1
meta = Meta(since=self.options.get('since'), meta = Meta(since=self.options.get('since'),
deprecated=self.options.get('deprecated')) deprecated=self.options.get('deprecated'),
synopsis=self.options.get('synopsis'),
index = num,
title = title)
env.domaindata['cabal']['pkg-sections'][section] = \ store = env.domaindata['cabal'][data_key]
env.docname, targetname, meta if not section in store:
store[section] = env.docname, targetname, meta
return [inode, node] return [inode, node]
class CabalPackageSection(CabalSection):
"""
Marks section in package.cabal file
"""
indextemplate = '%s; package.cabal section'
section_key = 'cabal:pkg-section'
target_prefix = 'pkg-section-'
class CabalField(ObjectDescription): class CabalField(ObjectDescription):
option_spec = { option_spec = {
'noindex': directives.flag, 'noindex': directives.flag,
'deprecated': parse_deprecated, 'deprecated': parse_deprecated,
'since' : StrictVersion 'since' : StrictVersion,
'synopsis' : lambda x:x
} }
doc_field_types = [ doc_field_types = [
Field('default', label='Default value', names=['default'], has_arg=False) Field('default', label='Default value', names=['default'], has_arg=False)
] ]
section_key = 'cabal:pkg-section'
indextemplate = '%s; package.cabal section'
def get_meta(self): def get_meta(self):
# find title of current section, will group references page by it
section = None
if isinstance(self.state.parent, nodes.section):
for kid in self.state.parent:
if isinstance(kid, nodes.title):
section = kid.astext()
return Meta(since=self.options.get('since'), return Meta(since=self.options.get('since'),
deprecated=self.options.get('deprecated')) deprecated=self.options.get('deprecated'),
synopsis=self.options.get('synopsis'))
def get_index_entry(self, env, name): def get_index_entry(self, env, name):
return name, self.objtype + '-' + name return name, self.objtype + '-' + name
def get_env_key(self, env, name): def get_env_key(self, env, name):
return self.name section = self.env.ref_context.get(self.section_key)
store = CabalDomain.types[self.objtype]
return (section, name), store
def get_index_entry(self, env, name):
section = self.env.ref_context.get(self.section_key)
if section is not None:
parts = (self.objtype, section, name)
indexentry = self.indextemplate % (section + ':' + name)
else:
parts = (self.objtype, name)
indexentry = self.indextemplate % name
targetname = '-'.join(parts)
return indexentry, targetname
def handle_signature(self, sig, signode): def handle_signature(self, sig, signode):
sig = sig.strip() sig = sig.strip()
...@@ -134,7 +221,7 @@ class CabalField(ObjectDescription): ...@@ -134,7 +221,7 @@ class CabalField(ObjectDescription):
meta = self.get_meta() meta = self.get_meta()
rendered = render_meta(meta) rendered = render_meta_title(meta)
if rendered != '': if rendered != '':
signode += addnodes.desc_addname(' ', ' ') signode += addnodes.desc_addname(' ', ' ')
signode += addnodes.desc_annotation(rendered, rendered) signode += addnodes.desc_annotation(rendered, rendered)
...@@ -153,45 +240,44 @@ class CabalField(ObjectDescription): ...@@ -153,45 +240,44 @@ class CabalField(ObjectDescription):
entries=[('pair', indexentry, targetname, '', None)]) entries=[('pair', indexentry, targetname, '', None)])
signode.insert(0, inode) signode.insert(0, inode)
meta = Meta(since=self.options.get('since'), meta = self.get_meta()
deprecated=self.options.get('deprecated'))
#for ref finding #for ref finding
key = self.get_env_key(env, name)
store = CabalDomain.types[self.objtype] key, store = self.get_env_key(env, name)
env.domaindata['cabal'][store][key] = env.docname, targetname, meta env.domaindata['cabal'][store][key] = env.docname, targetname, meta
class CabalPackageField(CabalField): class CabalPackageField(CabalField):
def get_index_entry(self, env, name): section_key = 'cabal:pkg-section'
section = self.env.ref_context.get('cabal:section') indextemplate = '%s; package.cabal field'
if section is not None:
parts = (self.objtype, section, name)
indexentry = section + ':' + name + '; package.cabal field'
else:
parts = (self.objtype, name)
indexentry = name + '; package.cabal field'
targetname = '-'.join(parts)
return indexentry, targetname
def get_env_key(self, env, name): class CabalFieldXRef(XRefRole):
section = env.ref_context.get('cabal:section') section_key = 'cabal:pkg-section'
return section, name
class CabalPackageFieldXRef(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target): def process_link(self, env, refnode, has_explicit_title, title, target):
parts = target.split(':',1) parts = target.split(':',1)
if len(parts) == 2: if len(parts) == 2:
section, target = parts section, target = parts
section = section.strip() section = section.strip()
target = target.strip() target = target.strip()
refnode['cabal:section'] = section refnode[self.section_key] = section
else: else:
refnode['cabal:section'] = env.ref_context.get('cabal:section') refnode[self.section_key] = env.ref_context.get(self.section_key)
return title, target return title, target
class CabalPackageFieldXRef(CabalFieldXRef):
section_key = 'cabal:pkg-section'
class CabalConfigSection(CabalSection):
"""
Marks section in package.cabal file
"""
indextemplate = '%s; project.cabal section'
section_key = 'cabal:cfg-section'
target_prefix = 'cfg-section-'
class ConfigField(CabalField): class ConfigField(CabalField):
section_key = 'cabal:cfg-section'
indextemplate = '%s ; cabal project option'
def handle_signature(self, sig, signode): def handle_signature(self, sig, signode):
sig = sig.strip() sig = sig.strip()
if sig.startswith('-'): if sig.startswith('-'):
...@@ -203,21 +289,81 @@ class ConfigField(CabalField): ...@@ -203,21 +289,81 @@ class ConfigField(CabalField):
def get_index_entry(self, env, name): def get_index_entry(self, env, name):
if name.startswith('-'): if name.startswith('-'):
parts = ('cfg-flag', name) section = self.env.ref_context.get(self.section_key)
if section is not None:
parts = ('cfg-flag', section, name)
indexname = section + ':' + name
else:
parts = ('cfg-flag', name)
indexname = name
indexentry = name + '; cabal project option' indexentry = name + '; cabal project option'
targetname = '-'.join(parts)
return indexentry, targetname
else: else:
parts = (self.objtype, name) return super(ConfigField,self).get_index_entry(env, name)
indexentry = name + '; project.cabal field'
targetname = '-'.join(parts)
return indexentry, targetname
def get_env_key(self, env, name): def get_env_key(self, env, name):
return name section = self.env.ref_context.get(self.section_key)
if name.startswith('-'):
return (section, name), 'cfg-flags'
return (section, name), 'cfg-fields'
class CabalConfigFieldXRef(CabalFieldXRef):
section_key = 'cabal:cfg-section'
class ConfigFieldIndex(Index):
name = 'projectindex'
localname = "Cabal reference"
shortname = "Reference"
def generate(self, docnames=None):
# (title, section store, fields store)
entries = [('project.cabal fields', 'cfg-sections', 'cfg-fields'),
('cabal project flags', 'cfg-sections', 'cfg-flags'),
('project.cabal fields', 'pkg-sections', 'pkg-fields')]
result = []
for label, section_key, key in entries:
# sort sections by (index, name)
sections = sorted(self.domain.data[section_key].items(),
key=lambda x: (x[1][2].index, x[0]))
data = {}
for (section, name), value in self.domain.data[key].items():
try:
data[section].append((name,value))
except KeyError:
data[section] = [(name,value)]
references = []
for section, (sec_doc, sec_anchor, sec_meta) in sections:
fields = data.get(section, [])
sec_extra = render_meta(sec_meta)
sec_descr = sec_meta.synopsis if sec_meta.synopsis is not None \
else sec_meta.title if sec_meta.title \
else section
references.append(
(sec_descr, 0, sec_doc, sec_anchor, sec_extra, '', ''))
fields.sort(key = lambda x: x[0])
for name, (doc, anchor, meta) in fields:
extra = render_meta(meta)
descr = meta.synopsis if meta.synopsis is not None else ''
field = (name, 2, doc, anchor, extra, '', descr)
references.append(field)
result.append((label, references))
return result, False
def make_data_keys(typ, target, node): def make_data_keys(typ, target, node):
if typ == 'pkg-field': if typ == 'pkg-field':
section = node.get('cabal:section') section = node.get('cabal:pkg-section')
return [(section, target),
(None, target),
('global', target),
('build', target)]
elif typ in ('cfg-field', 'cfg-flag'):
section = node.get('cabal:cfg-section')
return [(section, target), (None, target)] return [(section, target), (None, target)]
else: else:
return [target] return [target]
...@@ -225,29 +371,46 @@ def make_data_keys(typ, target, node): ...@@ -225,29 +371,46 @@ def make_data_keys(typ, target, node):
def render_meta(meta): def render_meta(meta):
if meta.deprecated is not None: if meta.deprecated is not None:
if isinstance(meta.deprecated, StrictVersion): if isinstance(meta.deprecated, StrictVersion):
return '(deprecated since:'+str(meta.deprecated) + ')' return 'deprecated since: '+str(meta.deprecated)
else: else:
return '(deprecated)' return 'deprecated'
elif meta.since is not None: elif meta.since is not None:
return '(since version: ' + str(meta.since) + ')' return 'since version: ' + str(meta.since)
else: else:
return '' return ''
def render_meta_title(meta):
rendered = render_meta(meta)
if rendered != '':
return '(' + rendered + ')'
return ''
def make_title(typ, key, meta): def make_title(typ, key, meta):
if typ == 'pkg-section': if typ == 'pkg-section':
return "package.cabal " + key + " section " + render_meta(meta) return "package.cabal " + key + " section " + render_meta_title(meta)
elif typ == 'pkg-field': elif typ == 'pkg-field':
section, name = key section, name = key
if section is not None: if section is not None:
base = "package.cabal " + section + " section " + name + " field" base = "package.cabal " + section + " section " + name + ": field"
else: else:
base = "package.cabal " + name + " field" base = "package.cabal " + name + " field"
return base + render_meta(meta) return base + render_meta_title(meta)
elif typ == 'cfg-section':
return "project.cabal " + key + " section " + render_meta_title(meta)
elif typ == 'cfg-field': elif typ == 'cfg-field':
return "project.cabal " + key + " field " + render_meta(meta) section, name = key
return "project.cabal " + name + " field " + render_meta_title(meta)
elif typ == 'cfg-flag':
section, name = key
return "cabal flag " + name + " " + render_meta_title(meta)
else:
raise ValueError("Unknown type: " + typ)
def make_full_name(typ, key, meta): def make_full_name(typ, key, meta):
if typ == 'pkg-section': if typ == 'pkg-section':
...@@ -272,37 +435,50 @@ class CabalDomain(Domain): ...@@ -272,37 +435,50 @@ class CabalDomain(Domain):
object_types = { object_types = {
'pkg-section': ObjType(l_('pkg-section'), 'pkg-section'), 'pkg-section': ObjType(l_('pkg-section'), 'pkg-section'),
'pkg-field' : ObjType(l_('pkg-field') , 'pkg-field' ), 'pkg-field' : ObjType(l_('pkg-field') , 'pkg-field' ),
'cfg-field' : ObjType(l_('cfg-field') , 'cfg-field' ), 'cfg-section': ObjType(l_('cfg-section'), 'cfg-section'),
'cfg-field' : ObjType(l_('cfg-field') , 'cfg-field' ),
} }
directives = { directives = {
'pkg-section': CabalPackageSection, 'pkg-section': CabalPackageSection,
'pkg-field' : CabalPackageField, 'pkg-field' : CabalPackageField,
'cfg-section': CabalConfigSection,
'cfg-field' : ConfigField, 'cfg-field' : ConfigField,
} }
roles = { roles = {
'pkg-section': XRefRole(warn_dangling=True), 'pkg-section': XRefRole(warn_dangling=True),
'pkg-field' : CabalPackageFieldXRef(warn_dangling=True), 'pkg-field' : CabalPackageFieldXRef(warn_dangling=True),
'cfg-field' : XRefRole(warn_dangling=True), 'cfg-section': XRefRole(warn_dangling=True),
'cfg-flag' : XRefRole(warn_dangling=True), 'cfg-field' : CabalConfigFieldXRef(warn_dangling=True),
'cfg-flag' : CabalConfigFieldXRef(warn_dangling=True),
} }
initial_data = { initial_data = {
'pkg-sections': {}, 'pkg-sections': {},
'pkg-fields' : {}, 'pkg-fields' : {},
'cfg-fields' : {}, 'cfg-sections': {},
'pkg-sections-num' : {}, #per document number of sections
'cfg-sections-num' : {}, #used to order references page
'cfg-fields' : {},
'cfg-flags' : {},
} }
indices = [ indices = [
ConfigFieldIndex
] ]
types = { types = {
'pkg-section': 'pkg-sections', 'pkg-section': 'pkg-sections',
'pkg-field' : 'pkg-fields', 'pkg-field' : 'pkg-fields',
'cfg-section': 'cfg-sections',
'cfg-field' : 'cfg-fields', 'cfg-field' : 'cfg-fields',
'cfg-flag' : 'cfg-flags',
} }
def clear_doc(self, docname): def clear_doc(self, docname):
for k in ['pkg-sections', 'pkg-fields', 'cfg-fields']: for k in ['pkg-sections', 'pkg-fields', 'cfg-sections',
'cfg-fields', 'cfg-flags']:
for name, (fn, _, _) in self.data[k].items(): for name, (fn, _, _) in self.data[k].items():
if fn == docname: if fn == docname:
del self.data[k][comname] del self.data[k][comname]
for k in ['pkg-sections-num', 'cfg-sections-num']:
if docname in self.data[k]:
self.data[k][docname]
def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode): def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode):
objtypes = self.objtypes_for_role(type) objtypes = self.objtypes_for_role(type)
...@@ -317,7 +493,8 @@ class CabalDomain(Domain): ...@@ -317,7 +493,8 @@ class CabalDomain(Domain):
return make_refnode(builder, fromdocname, doc, ref, contnode, title) return make_refnode(builder, fromdocname, doc, ref, contnode, title)
def get_objects(self): def get_objects(self):
for typ in ['pkg-section', 'pkg-field', 'cfg-field']: for typ in ['pkg-section', 'pkg-field',
'cfg-section', 'cfg-field', 'cfg-flag']:
key = self.types[typ] key = self.types[typ]
for name, (fn, target, meta) in self.data[key].items(): for name, (fn, target, meta) in self.data[key].items():
title = make_title(typ, name, meta) title = make_title(typ, name, meta)
......
...@@ -736,11 +736,11 @@ mandatory. ...@@ -736,11 +736,11 @@ mandatory.
Some fields are marked as required. All others are optional, and unless Some fields are marked as required. All others are optional, and unless
otherwise specified have empty default values. otherwise specified have empty default values.
.. pkg-section:: None