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 @@
{% block menu %}
{{ super() }}
<a href="genindex.html">Index</a>
<a href="cabal-projectindex.html">Reference</a>
{% endblock %}
......@@ -11,7 +11,7 @@ from distutils.version import StrictVersion
from sphinx import addnodes
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.locale import l_, _
from sphinx.roles import XRefRole
......@@ -55,71 +55,158 @@ class Meta(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.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
and adds it to index. Can be referenced with pkg-section-name.
Marks section to which following objects belong.
Does not generate any output besides anchor
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {
'name': lambda x: x,
'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):
env = self.state.document.settings.env
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':
env.ref_context.pop('cabal:section', None)
env.ref_context.pop(self.section_key, None)
return []
env.ref_context['cabal:section'] = section
targetname = 'pkg-section-' + section
env.ref_context[self.section_key] = section
targetname = self.target_prefix + section
node = nodes.target('', '', ids=[targetname])
self.state.document.note_explicit_target(node)
indexentry = section + '; package.cabal section'
inode = addnodes.index(entries=[('pair', indexentry,
targetname, '', None)])
indexentry = self.get_index_entry(section)
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'),
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] = \
env.docname, targetname, meta
store = env.domaindata['cabal'][data_key]
if not section in store:
store[section] = env.docname, targetname, meta
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):
option_spec = {
'noindex': directives.flag,
'deprecated': parse_deprecated,
'since' : StrictVersion
'since' : StrictVersion,
'synopsis' : lambda x:x
}
doc_field_types = [
Field('default', label='Default value', names=['default'], has_arg=False)
]
section_key = 'cabal:pkg-section'
indextemplate = '%s; package.cabal section'
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'),
deprecated=self.options.get('deprecated'))
deprecated=self.options.get('deprecated'),
synopsis=self.options.get('synopsis'))
def get_index_entry(self, env, name):
return name, self.objtype + '-' + 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):
sig = sig.strip()
......@@ -134,7 +221,7 @@ class CabalField(ObjectDescription):
meta = self.get_meta()
rendered = render_meta(meta)
rendered = render_meta_title(meta)
if rendered != '':
signode += addnodes.desc_addname(' ', ' ')
signode += addnodes.desc_annotation(rendered, rendered)
......@@ -153,45 +240,44 @@ class CabalField(ObjectDescription):
entries=[('pair', indexentry, targetname, '', None)])
signode.insert(0, inode)
meta = Meta(since=self.options.get('since'),
deprecated=self.options.get('deprecated'))
meta = self.get_meta()
#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
class CabalPackageField(CabalField):
def get_index_entry(self, env, name):
section = self.env.ref_context.get('cabal:section')
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
section_key = 'cabal:pkg-section'
indextemplate = '%s; package.cabal field'
def get_env_key(self, env, name):
section = env.ref_context.get('cabal:section')
return section, name
class CabalPackageFieldXRef(XRefRole):
class CabalFieldXRef(XRefRole):
section_key = 'cabal:pkg-section'
def process_link(self, env, refnode, has_explicit_title, title, target):
parts = target.split(':',1)
if len(parts) == 2:
section, target = parts
section = section.strip()
target = target.strip()
refnode['cabal:section'] = section
refnode[self.section_key] = section
else:
refnode['cabal:section'] = env.ref_context.get('cabal:section')
refnode[self.section_key] = env.ref_context.get(self.section_key)
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):
section_key = 'cabal:cfg-section'
indextemplate = '%s ; cabal project option'
def handle_signature(self, sig, signode):
sig = sig.strip()
if sig.startswith('-'):
......@@ -203,21 +289,81 @@ class ConfigField(CabalField):
def get_index_entry(self, env, name):
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'
targetname = '-'.join(parts)
return indexentry, targetname
else:
parts = (self.objtype, name)
indexentry = name + '; project.cabal field'
targetname = '-'.join(parts)
return indexentry, targetname
return super(ConfigField,self).get_index_entry(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):
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)]
else:
return [target]
......@@ -225,29 +371,46 @@ def make_data_keys(typ, target, node):
def render_meta(meta):
if meta.deprecated is not None:
if isinstance(meta.deprecated, StrictVersion):
return '(deprecated since:'+str(meta.deprecated) + ')'
return 'deprecated since: '+str(meta.deprecated)
else:
return '(deprecated)'
return 'deprecated'
elif meta.since is not None:
return '(since version: ' + str(meta.since) + ')'
return 'since version: ' + str(meta.since)
else:
return ''
def render_meta_title(meta):
rendered = render_meta(meta)
if rendered != '':
return '(' + rendered + ')'
return ''
def make_title(typ, key, meta):
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':
section, name = key
if section is not None:
base = "package.cabal " + section + " section " + name + " field"
base = "package.cabal " + section + " section " + name + ": field"
else:
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':
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):
if typ == 'pkg-section':
......@@ -272,37 +435,50 @@ class CabalDomain(Domain):
object_types = {
'pkg-section': ObjType(l_('pkg-section'), 'pkg-section'),
'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 = {
'pkg-section': CabalPackageSection,
'pkg-field' : CabalPackageField,
'cfg-section': CabalConfigSection,
'cfg-field' : ConfigField,
}
roles = {
'pkg-section': XRefRole(warn_dangling=True),
'pkg-field' : CabalPackageFieldXRef(warn_dangling=True),
'cfg-field' : XRefRole(warn_dangling=True),
'cfg-flag' : XRefRole(warn_dangling=True),
'cfg-section': XRefRole(warn_dangling=True),
'cfg-field' : CabalConfigFieldXRef(warn_dangling=True),
'cfg-flag' : CabalConfigFieldXRef(warn_dangling=True),
}
initial_data = {
'pkg-sections': {},
'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 = [
ConfigFieldIndex
]
types = {
'pkg-section': 'pkg-sections',
'pkg-field' : 'pkg-fields',
'cfg-section': 'cfg-sections',
'cfg-field' : 'cfg-fields',
'cfg-flag' : 'cfg-flags',
}
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():
if fn == docname:
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):
objtypes = self.objtypes_for_role(type)
......@@ -317,7 +493,8 @@ class CabalDomain(Domain):
return make_refnode(builder, fromdocname, doc, ref, contnode, title)
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]
for name, (fn, target, meta) in self.data[key].items():
title = make_title(typ, name, meta)
......
......@@ -736,11 +736,11 @@ mandatory.
Some fields are marked as required. All others are optional, and unless
otherwise specified have empty default values.
.. pkg-section:: None
Package properties
^^^^^^^^^^^^^^^^^^
.. pkg-section:: global
These fields may occur in the first top-level properties section and
describe the package as a whole:
......@@ -984,11 +984,11 @@ describe the package as a whole:
additional hooks, such as the scheme described in the section on
`system-dependent parameters`_
.. pkg-section:: library
Library
^^^^^^^
.. pkg-section:: library
The library section should contain the following fields:
.. pkg-field:: exposed-modules: identifier list
......@@ -1144,11 +1144,11 @@ For example, given the following dependencies specified in
foo >= 0.5.2 && < 0.6
bar >= 1.1 && < 1.2
.. pkg-section:: executable
Executables
^^^^^^^^^^^
.. pkg-section:: executable
Executable sections (if present) describe executable programs contained
in the package and must have an argument after the section label, which
defines the name of the executable. This is a freeform argument but may
......@@ -1182,11 +1182,11 @@ executable defined in the whole package, the executable's name can be
omitted. See the output of ``cabal help run`` for a list of options you
can pass to ``cabal run``.
.. pkg-section:: test
Test suites
^^^^^^^^^^^
.. pkg-section:: test
Test suite sections (if present) describe package test suites and must
have an argument after the section label, which defines the name of the
test suite. This is a freeform argument, but may not contain spaces. It
......@@ -1334,11 +1334,11 @@ You can have Cabal run your test suites using its built-in test runner:
See the output of ``cabal help test`` for a list of options you can pass
to ``cabal test``.
.. pkg-section:: benchmark
Benchmarks
^^^^^^^^^^
.. pkg-section:: benchmark
Benchmark sections (if present) describe benchmarks contained in the
package and must have an argument after the section label, which defines
the name of the benchmark. This is a freeform argument, but may not
......@@ -1425,11 +1425,11 @@ runner:
See the output of ``cabal help bench`` for a list of options you can
pass to ``cabal bench``.
.. pkg-section:: None
Build information
^^^^^^^^^^^^^^^^^
.. pkg-section:: build
The following fields may be optionally present in a library, executable,
test suite or benchmark section, and give information for the building
of the corresponding library or executable. See also the sections on
......@@ -1843,11 +1843,11 @@ Example: Using explicit braces rather than indentation for layout
}
}
.. pkg-section:: flag
Configuration Flags
"""""""""""""""""""
.. pkg-section:: flags
A flag section takes the flag name as an argument and may contain the
following fields.
......@@ -2039,11 +2039,11 @@ and outside then they are combined using the following rules.
else
Main-is: Main.hs
.. pkg-section source-repository::
Source Repositories
^^^^^^^^^^^^^^^^^^^
.. pkg-section:: source-repository
It is often useful to be able to specify a source revision control
repository for a package. Cabal lets you specifying this information in
a relatively structured form which enables other tools to interpret and
......@@ -2175,12 +2175,12 @@ The ``get`` command supports the following options:
control system. The optional argument allows to choose a specific
repository kind.
.. pkg-section:: custom-setup
:since: 1.24
Custom setup scripts
--------------------
.. pkg-section:: custom-setup
:since: 1.24
The optional :pkg-section:`custom-setup` stanza contains information needed for
the compilation of custom ``Setup.hs`` scripts,
......
......@@ -405,10 +405,13 @@ following sources (later entries override earlier ones):
Specifying the local packages
-----------------------------
.. cfg-section:: packages
The following top-level options specify what the local packages of a
project are:
.. cfg-field:: packages: package location list (space or comma separated)
:synopsis: Project packages.