make sure cabal-install is compatible with Cabal
[cabal.git] / doc / cabaldomain.py
blob9e16aefa6d61a71694542049980a391dc99b8534
1 # -*- coding: utf-8 -*-
2 '''
3 Sphinx domain for documenting all things cabal
5 The main reason to use this instead of adding object types to std domain
6 is the ability to generate nice 'Reference' page and also provide some meta
7 data for objects described with directives described here.
9 Most directives have at least following optional arguments
11 `:since: 1.24`
12 version of Cabal in which feature was added.
14 `:deprecated: 1.24`
15 `:deprecated:`
16 Feature was deprecated, and optionally since which version.
18 `:removed: 3.0`
19 Feature was removed
21 `:synopsis: Short desc`
22 Text used as short description on reference page.
25 Added directives
27 .. rst:directive:: .. cabal::cfg-section
29 Describes a package.cabal section, such as library or executable.
31 All following `pkg-field` directives will add section name
32 to their fields name for disambiguating duplicates.
34 You can reset the section disambiguation with `.. pkg-section:: None`.
36 .. rst::role:: pkg-section
38 References section added by `.. pkg-section`
40 .. rst:directive:: .. cabal::pkg-field
42 Describes a package.cabal field.
44 Can have a :default: field. Will group on reference page under pkg-section
45 if set and parent header otherwise.
47 .. rst::role:: pkg-field
49 References field added by `.. pkg-field`, fields can be disambiguated
50 with section name `:pkg-field:`section:field`.
53 .. rst:directive:: .. cabal:cfg-section::
55 Same as `.. cabal::pkg-section` but does not produce any visible output
56 currently unused.
58 .. rst:directive:: .. cabal:cfg-field::
60 Describes a cabal.project field.
62 Can have multiple arguments, if arguments start with '-' then it is treated
63 as a cabal flag.
65 Can have a :default: field. Will group on reference page under pkg-section
66 if set and parent header otherwise.
68 .. rst::role:: cfg-field
70 References field added by `.. cfg-field`.
72 .. rst::role:: cfg-flag
74 References flag added by `.. cfg-field`.
77 All roles can be supplied with title as in standard sphinx references::
79 :pkg-field:`Build dependencies<build-depends>`
82 To be done:
84 - Directives for describing executables, their subcommands and flags.
86 These should act in a way similar to `.. std::option` directive, but with
87 extra meta. And should also end up in reference.
89 At least setup and 'new-build` subcommands should get special directives
91 - Improve rendering of flags in `.. cfg-field::` directive. It should be
92 possible without copy-pasting code from sphinx.directives.ObjectDescription
93 by examining result of ObjectDescription.run and inserting flags into
94 desc_content node.
96 Alternatively Or `.. flags::` sub-directive can be added which will be aware
97 of parent `.. cfg-field` directive.
99 - With same ObjectDescription.run trick as above, render since and deprecated
100 info same way as standard object fields, and use fancy rendering only on
101 references page.
103 - Add 'since_version` config value to sphinx env and use it to decide if
104 version meta info should be rendered on reference page and thus reduce some
105 clutter.
106 Can also be used to generate 'Whats new' reference page
111 import re
113 from docutils import nodes
114 from docutils.parsers.rst import Directive, directives, roles
116 import pygments.lexer as lexer
117 import pygments.token as token
119 from distutils.version import StrictVersion
121 from sphinx import addnodes
122 from sphinx.directives import ObjectDescription
123 from sphinx.domains import ObjType, Domain, Index
124 from sphinx.domains.std import StandardDomain
125 from sphinx.locale import _
126 from sphinx.roles import XRefRole
127 from sphinx.util.docfields import Field, DocFieldTransformer
128 from sphinx.util.nodes import make_refnode
130 def parse_deprecated(txt):
131 if txt is None:
132 return True
133 try:
134 return StrictVersion(txt)
135 except ValueError:
136 return True
138 def parse_flag(env, sig, signode):
139 import re
140 names = []
141 for i, flag in enumerate(sig.split(',')):
142 flag = flag.strip()
143 sep = '='
144 parts = flag.split('=')
145 if len(parts) == 1:
146 sep=' '
147 parts = flag.split()
148 if len(parts) == 0: continue
150 name = parts[0]
151 names.append(name)
152 sig = sep + ' '.join(parts[1:])
153 sig = re.sub(r'<([-a-zA-Z ]+)>', r'⟨\1⟩', sig)
154 if i > 0:
155 signode += addnodes.desc_name(', ', ', ')
156 signode += addnodes.desc_name(name, name)
157 if len(sig) > 0:
158 signode += addnodes.desc_addname(sig, sig)
160 return names[0]
163 class Meta(object):
165 Meta data associated with object
167 def __init__(self,
168 since=None,
169 deprecated=None,
170 removed=None,
171 synopsis=None,
172 title=None,
173 section=None,
174 index=0):
175 self.since = since
176 self.deprecated = deprecated
177 self.removed = removed
178 self.synopsis = synopsis
179 self.title = title
180 self.section = section
181 self.index = index
184 def find_section_title(parent):
186 Find current section id and title if possible
188 while parent is not None:
189 if isinstance(parent, nodes.section):
190 break
191 parent = parent.parent
193 if parent is None:
194 return None
196 section_id = parent['ids'][0]
197 section_name = parent['names'][0]
199 for kid in parent:
200 if isinstance(kid, nodes.title):
201 return kid.astext(), section_id
203 print(section_name, section_id)
204 return section_name, section_id
207 class CabalSection(Directive):
209 Marks section to which following objects belong, used to disambiguate
210 references to fields and flags which can have similar names
212 Does not generate any output besides anchor.
214 has_content = False
215 required_arguments = 1
216 optional_arguments = 0
217 final_argument_whitespace = True
218 option_spec = {
219 'name': lambda x: x,
220 'deprecated': parse_deprecated,
221 'removed': StrictVersion,
222 'since' : StrictVersion,
223 'synopsis' : lambda x:x,
225 section_key = 'cabal:pkg-section'
226 target_prefix = 'pkg-section-'
227 indextemplate = ''
228 indextype = 'pair'
230 def get_index_entry(self, name):
231 return self.indextemplate % name
233 def run(self):
234 env = self.state.document.settings.env
235 section = self.arguments[0].strip()
237 if ':' in self.name:
238 self.domain, self.objtype = self.name.split(':', 1)
239 else:
240 self.domain, self.objtype = '', self.name
242 if section == 'None':
243 env.ref_context.pop(self.section_key, None)
244 return []
246 env.ref_context[self.section_key] = section
247 targetname = self.target_prefix + section
248 node = nodes.target('', '', ids=[targetname])
249 self.state.document.note_explicit_target(node)
251 indexentry = self.get_index_entry(section)
253 inode = addnodes.index(
254 entries = [
255 (self.indextype, indexentry, targetname, '', None)])
257 # find title of parent section node
258 title = find_section_title(self.state.parent)
260 data_key = CabalDomain.types[self.objtype]
262 # find how many sections in this document were added
263 num = env.domaindata['cabal']['index-num'].get(env.docname, 0)
264 env.domaindata['cabal']['index-num'][env.docname] = num + 1
266 meta = Meta(since=self.options.get('since'),
267 deprecated=self.options.get('deprecated'),
268 removed=self.options.get('removed'),
269 synopsis=self.options.get('synopsis'),
270 index = num,
271 title = title)
273 store = env.domaindata['cabal'][data_key]
274 if not section in store:
275 store[section] = env.docname, targetname, meta
277 return [inode, node]
280 class CabalObject(ObjectDescription):
281 option_spec = {
282 'noindex' : directives.flag,
283 'deprecated': parse_deprecated,
284 'removed' : StrictVersion,
285 'since' : StrictVersion,
286 'synopsis' : lambda x:x
289 # node attribute marking which section field belongs to
290 section_key = ''
291 # template for index, it is passed a field name as argument
292 # used by default deg_index_entry method
293 indextemplate = ''
295 def get_meta(self):
297 Collect meta data for fields
299 Reads optional arguments passed to directive and also
300 tries to find current section title and adds it as section
302 env = self.state.document.settings.env
303 # find title of current section, will group references page by it
304 num = env.domaindata['cabal']['index-num'].get(env.docname, 0)
305 env.domaindata['cabal']['index-num'][env.docname] = num + 1
307 title = find_section_title(self.state.parent)
308 return Meta(since=self.options.get('since'),
309 deprecated=self.options.get('deprecated'),
310 removed=self.options.get('removed'),
311 title=title,
312 index = num,
313 synopsis=self.options.get('synopsis'))
315 def get_env_key(self, env, name):
317 Should return a key used to reference this field and key in domain
318 data to store this object
320 section = self.env.ref_context.get(self.section_key)
321 store = CabalDomain.types[self.objtype]
322 return (section, name), store
324 def get_index_entry(self, env, name):
326 Should return index entry and anchor
328 By default uses indextemplate attribute to generate name and
329 index entry by joining directive name, section and field name
331 section = self.env.ref_context.get(self.section_key)
333 if section is not None:
334 parts = (self.objtype, section, name)
335 indexentry = self.indextemplate % (section + ':' + name)
336 else:
337 parts = (self.objtype, name)
338 indexentry = self.indextemplate % name
340 targetname = '-'.join(parts)
341 return indexentry, targetname
344 def add_target_and_index(self, name, sig, signode):
346 As in sphinx.directive.ObjectDescription
348 By default adds 'pair' index as returned by get_index_entry and
349 stores object data into domain data store as returned by get_env_data
351 env = self.state.document.settings.env
353 indexentry, targetname = self.get_index_entry(self, name)
355 signode['ids'].append(targetname)
356 self.state.document.note_explicit_target(signode)
358 inode = addnodes.index(
359 entries=[('pair', indexentry, targetname, '', None)])
360 signode.insert(0, inode)
362 key, store = self.get_env_key(env, name)
363 env.domaindata['cabal'][store][key] = env.docname, targetname, self.cabal_meta
365 def run(self):
366 self.cabal_meta = self.get_meta()
367 result = super(CabalObject, self).run()
369 if self.cabal_meta.since is not None \
370 or self.cabal_meta.deprecated is not None:
372 #find content part of description
373 for item in result:
374 if isinstance(item, addnodes.desc):
375 desc = item
376 break
377 else:
378 return result
380 for item in desc:
381 if isinstance(item, addnodes.desc_content):
382 contents = item
383 break
384 else:
385 return result
387 # find exsting field list and add to it
388 # or create new one
389 for item in contents:
390 if isinstance(item, nodes.field_list):
391 field_list = item
392 break
393 else:
394 field_list = nodes.field_list('')
395 contents.insert(0, field_list)
398 if self.cabal_meta.since is not None:
399 #docutils horror
400 field = nodes.field('')
401 field_name = nodes.field_name('Since', 'Since')
402 since = 'Cabal ' + str(self.cabal_meta.since)
403 field_body = nodes.field_body(since, nodes.paragraph(since, since))
404 field += field_name
405 field += field_body
406 field_list.insert(0, field)
408 if self.cabal_meta.deprecated is not None:
409 field = nodes.field('')
410 field_name = nodes.field_name('Deprecated', 'Deprecated')
411 if isinstance(self.cabal_meta.deprecated, StrictVersion):
412 since = 'Cabal ' + str(self.cabal_meta.deprecated)
413 else:
414 since = ''
416 field_body = nodes.field_body(since, nodes.paragraph(since, since))
417 field += field_name
418 field += field_body
419 field_list.insert(0, field)
421 if self.cabal_meta.removed is not None:
422 field = nodes.field('')
423 field_name = nodes.field_name('Removed', 'Removed')
424 if isinstance(self.cabal_meta.removed, StrictVersion):
425 since = 'Cabal ' + str(self.cabal_meta.removed)
426 else:
427 since = ''
429 field_body = nodes.field_body(since, nodes.paragraph(since, since))
430 field += field_name
431 field += field_body
432 field_list.insert(0, field)
433 return result
435 class CabalPackageSection(CabalObject):
437 Cabal section in package.cabal file
439 section_key = 'cabal:pkg-section'
440 indextemplate = '%s; package.cabal section'
442 def handle_signature(self, sig, signode):
444 As in sphinx.directives.ObjectDescription
446 By default make an object description from name and adding
447 either deprecated or since as annotation.
449 env = self.state.document.settings.env
451 sig = sig.strip()
452 parts = sig.split(' ',1)
453 name = parts[0]
454 signode += addnodes.desc_name(name, name)
455 signode += addnodes.desc_addname(' ', ' ')
456 if len(parts) > 1:
457 rest = parts[1].strip()
458 signode += addnodes.desc_annotation(rest, rest)
460 return name
462 def get_env_key(self, env, name):
463 store = CabalDomain.types[self.objtype]
464 return name, store
466 def run(self):
467 env = self.state.document.settings.env
468 section = self.arguments[0].strip().split(' ',1)[0]
469 if section == 'None':
470 env.ref_context.pop('cabal:pkg-section', None)
471 return []
472 env.ref_context['cabal:pkg-section'] = section
473 return super(CabalPackageSection, self).run()
476 class CabalField(CabalObject):
478 Base for fields in *.cabal files
480 option_spec = {
481 'noindex' : directives.flag,
482 'deprecated': parse_deprecated,
483 'removed' : StrictVersion,
484 'since' : StrictVersion,
485 'synopsis' : lambda x:x
488 doc_field_types = [
489 Field('default', label='Default value', names=['default'], has_arg=False)
492 def handle_signature(self, sig, signode):
494 As in sphinx.directives.ObjectDescription
496 By default make an object description from name and adding
497 either deprecated or since as annotation.
499 env = self.state.document.settings.env
501 sig = sig.strip()
502 parts = sig.split(':',1)
503 name = parts[0]
504 signode += addnodes.desc_name(name, name)
505 signode += addnodes.desc_addname(': ', ': ')
507 if len(parts) > 1:
508 rest = parts[1].strip()
509 signode += addnodes.desc_annotation(rest, rest)
511 return name
513 class CabalPackageField(CabalField):
515 Describes section in package.cabal file
517 section_key = 'cabal:pkg-section'
518 indextemplate = '%s; package.cabal field'
520 class CabalFieldXRef(XRefRole):
522 Cross ref node for all kinds of fields
524 Gets section_key entry from context and stores it on node, so it can
525 later be used by CabalDomain.resolve_xref to find target for reference to
526 this
528 section_key = 'cabal:pkg-section'
529 def process_link(self, env, refnode, has_explicit_title, title, target):
530 parts = target.split(':',1)
531 if len(parts) == 2:
532 section, target = parts
533 section = section.strip()
534 target = target.strip()
535 refnode[self.section_key] = section
536 else:
537 refnode[self.section_key] = env.ref_context.get(self.section_key)
539 return title, target
542 # Directives for config files.
545 class CabalPackageFieldXRef(CabalFieldXRef):
547 Role referencing cabal.project section
549 section_key = 'cabal:pkg-section'
551 class CabalConfigSection(CabalObject):
553 Marks section in package.cabal file
555 indextemplate = '%s; cabal.project section'
556 section_key = 'cabal:cfg-section'
557 target_prefix = 'cfg-section-'
559 def handle_signature(self, sig, signode):
561 As in sphinx.directives.ObjectDescription
563 By default make an object description from name and adding
564 either deprecated or since as annotation.
566 env = self.state.document.settings.env
568 sig = sig.strip()
569 parts = sig.split(' ',1)
570 name = parts[0]
571 signode += addnodes.desc_name(name, name)
572 signode += addnodes.desc_addname(' ', ' ')
573 if len(parts) > 1:
574 rest = parts[1].strip()
575 signode += addnodes.desc_annotation(rest, rest)
577 return name
579 def get_env_key(self, env, name):
580 store = CabalDomain.types[self.objtype]
581 return name, store
583 def run(self):
584 env = self.state.document.settings.env
585 section = self.arguments[0].strip().split(' ',1)[0]
586 if section == 'None':
587 env.ref_context.pop('cabal:cfg-section', None)
588 return []
589 env.ref_context['cabal:cfg-section'] = section
590 return super(CabalConfigSection, self).run()
592 class ConfigField(CabalField):
593 section_key = 'cabal:cfg-section'
594 indextemplate = '%s ; cabal project option'
595 def handle_signature(self, sig, signode):
596 sig = sig.strip()
597 if sig.startswith('-'):
598 name = parse_flag(self, sig, signode)
599 else:
600 name = super(ConfigField, self).handle_signature(sig, signode)
602 return name
604 def get_index_entry(self, env, name):
605 if name.startswith('-'):
606 section = self.env.ref_context.get(self.section_key)
607 if section is not None:
608 parts = ('cfg-flag', section, name)
609 indexname = section + ':' + name
610 else:
611 parts = ('cfg-flag', name)
612 indexname = name
613 indexentry = name + '; cabal project option'
614 targetname = '-'.join(parts)
615 return indexentry, targetname
616 else:
617 return super(ConfigField,self).get_index_entry(env, name)
619 def get_env_key(self, env, name):
620 section = self.env.ref_context.get(self.section_key)
621 if name.startswith('-'):
622 return (section, name), 'cfg-flags'
623 return (section, name), 'cfg-fields'
625 class CabalConfigFieldXRef(CabalFieldXRef):
626 section_key = 'cabal:cfg-section'
630 # Cabal domain
633 class ConfigFieldIndex(Index):
634 name = 'syntax-quicklinks'
635 localname = "Cabal Syntax Quicklinks"
636 shortname = "Quicklinks"
638 class Entry(object):
639 def __init__(self, typ, name, doc, anchor, meta):
640 self.type = typ
641 self.name = name
642 self.doc = doc
643 self.anchor = anchor
644 self.meta = meta
646 def _gather_data(self, obj_types):
648 Gather objects and return [(title, [Entry])]
650 def massage(typ, datum):
651 name, (doc, anchor, meta) = datum
652 return self.Entry(typ, name, doc, anchor, meta)
654 fields = []
655 for typ in obj_types:
656 store = CabalDomain.types[typ]
657 fields += [massage(typ, x)
658 for x in self.domain.data[store].items()]
660 fields.sort(key=lambda x: (x.doc, x.meta.index))
662 if len(fields) == 0:
663 return []
665 result = []
666 current = []
667 current_title = fields[0].meta.title
668 for field in fields:
669 if field.meta.title != current_title:
670 result.append((current_title, current))
671 current = []
672 current_title = field.meta.title
673 current.append(field)
674 result.append((current_title, current))
676 return result
679 def generate(self, docnames=None):
681 Try to group entries such that if entry has a section then put it
682 into same group.
684 Otherwise group it under same `title`.
686 Try to keep in same order as it was defined.
688 sort by (document, index)
689 group on (document, doc_section)
691 TODO: Check how to extract section numbers from (document,doc_section)
692 and add it as annotation to titles
695 # (title, section store, fields store)
696 entries = [('cabal.project fields', 'cfg-section', 'cfg-field'),
697 ('cabal project flags', 'cfg-section', 'cfg-flag'),
698 ('package.cabal fields', 'pkg-section', 'pkg-field')]
700 result = []
701 for label, section_key, key in entries:
703 data = self._gather_data([section_key, key])
705 references = []
706 for section, entries in data:
707 if section is None:
708 elem_type = 0 # Normal entry
709 else:
710 elem_type = 2 # sub_entry
712 assert len(entries) != 0
713 docname = entries[0].doc
714 if section is not None:
715 section_title, section_anchor = section
716 references.append(
717 (section_title, 1, docname, section_anchor, '', '', ''))
719 for entry in entries:
720 #todo deal with if
721 if isinstance(entry.name, tuple):
722 name = entry.name[1]
723 else:
724 name = entry.name
726 meta = entry.meta
727 extra = render_meta(meta)
728 descr = meta.synopsis if meta.synopsis is not None else ''
729 field = (name, elem_type, docname,
730 entry.anchor, extra, '', descr)
731 references.append(field)
732 result.append((label, references))
734 return result, False
736 def make_data_keys(typ, target, node):
738 Returns a list of keys to search for targets of this type
739 in domain data.
741 Used for resolving references
743 if typ == 'pkg-field':
744 section = node.get('cabal:pkg-section')
745 return [(section, target),
746 (None, target)]
747 elif typ in ('cfg-field', 'cfg-flag'):
748 section = node.get('cabal:cfg-section')
749 return [(section, target), (None, target)]
750 else:
751 return [target]
754 def render_deprecated(deprecated):
755 if isinstance(deprecated, StrictVersion):
756 return 'deprecated since: '+str(deprecated)
757 else:
758 return 'deprecated'
760 def render_removed(deprecated, removed):
761 if isinstance(deprecated, StrictVersion):
762 return 'removed in: ' + str(removed) + '; deprecated since: '+str(deprecated)
763 else:
764 return 'removed in: ' + str(removed)
766 def render_meta(meta):
768 Render meta as short text
770 Will render either deprecated or since info
772 if meta.removed is not None:
773 return render_removed(meta.deprecated, meta.removed)
774 if meta.deprecated is not None:
775 return render_deprecated(meta.deprecated)
776 elif meta.since is not None:
777 return 'since version: ' + str(meta.since)
778 else:
779 return ''
781 def render_meta_title(meta):
783 Render meta as suitable to use in titles
785 rendered = render_meta(meta)
786 if rendered != '':
787 return '(' + rendered + ')'
788 return ''
790 def make_title(typ, key, meta):
792 Render title of an object (section, field or flag)
794 if typ == 'pkg-section':
795 return "package.cabal " + key + " section " + render_meta_title(meta)
797 elif typ == 'pkg-field':
798 section, name = key
799 if section is not None:
800 base = "package.cabal " + section + " section " + name + ": field"
801 else:
802 base = "package.cabal " + name + " field"
804 return base + render_meta_title(meta)
806 elif typ == 'cfg-section':
807 return "cabal.project " + key + " section " + render_meta_title(meta)
809 elif typ == 'cfg-field':
810 section, name = key
811 return "cabal.project " + name + " field " + render_meta_title(meta)
813 elif typ == 'cfg-flag':
814 section, name = key
815 return "cabal flag " + name + " " + render_meta_title(meta)
817 else:
818 raise ValueError("Unknown type: " + typ)
820 def make_full_name(typ, key, meta):
822 Return an anchor name for object type
824 if typ == 'pkg-section':
825 return 'pkg-section-' + key
827 elif typ == 'cfg-section':
828 return 'cfg-section-' + key
830 elif typ == 'pkg-field':
831 section, name = key
832 if section is not None:
833 return '-'.join(('pkg-field',section, name))
834 else:
835 return 'pkg-field-' + name
837 elif typ == 'cfg-field':
838 return 'cfg-field-' + key
840 else:
841 raise ValueError('Unknown object type: ' + typ)
843 class CabalDomain(Domain):
845 Sphinx domain for cabal
847 needs Domain.merge_doc for parallel building, just union all dicts
849 name = 'cabal'
850 label = 'Cabal'
851 object_types = {
852 'pkg-section': ObjType(_('pkg-section'), 'pkg-section'),
853 'pkg-field' : ObjType(_('pkg-field') , 'pkg-field' ),
854 'cfg-section': ObjType(_('cfg-section'), 'cfg-section'),
855 'cfg-field' : ObjType(_('cfg-field') , 'cfg-field' ),
857 directives = {
858 'pkg-section': CabalPackageSection,
859 'pkg-field' : CabalPackageField,
860 'cfg-section': CabalConfigSection,
861 'cfg-field' : ConfigField,
863 roles = {
864 'pkg-section': XRefRole(warn_dangling=True),
865 'pkg-field' : CabalPackageFieldXRef(warn_dangling=True),
866 'cfg-section': XRefRole(warn_dangling=True),
867 'cfg-field' : CabalConfigFieldXRef(warn_dangling=True),
868 'cfg-flag' : CabalConfigFieldXRef(warn_dangling=True),
870 initial_data = {
871 'pkg-sections': {},
872 'pkg-fields' : {},
873 'cfg-sections': {},
874 'index-num' : {}, #per document number of objects
875 # used to order references page
876 'cfg-fields' : {},
877 'cfg-flags' : {},
879 indices = [
880 ConfigFieldIndex
882 types = {
883 'pkg-section': 'pkg-sections',
884 'pkg-field' : 'pkg-fields',
885 'cfg-section': 'cfg-sections',
886 'cfg-field' : 'cfg-fields',
887 'cfg-flag' : 'cfg-flags',
889 def clear_doc(self, docname):
890 for k in ['pkg-sections', 'pkg-fields', 'cfg-sections',
891 'cfg-fields', 'cfg-flags']:
892 to_del = []
893 for name, (fn, _, _) in self.data[k].items():
894 if fn == docname:
895 to_del.append(name)
896 for name in to_del:
897 del self.data[k][name]
898 try:
899 del self.data['index-num'][docname]
900 except KeyError:
901 pass
903 def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode):
904 objtypes = self.objtypes_for_role(type)
905 for typ, key in ((typ, key)
906 for typ in objtypes
907 for key in make_data_keys(typ, target, node)):
908 try:
909 data = env.domaindata['cabal'][self.types[typ]][key]
910 except KeyError:
911 continue
912 doc, ref, meta = data
913 title = make_title(typ, key, meta)
914 return make_refnode(builder, fromdocname, doc, ref, contnode, title)
916 def get_objects(self):
918 Used for search functionality
920 for typ in ['pkg-section', 'pkg-field',
921 'cfg-section', 'cfg-field', 'cfg-flag']:
922 key = self.types[typ]
923 for name, (fn, target, meta) in self.data[key].items():
924 title = make_title(typ, name, meta)
925 yield title, title, typ, fn, target, 0
927 class CabalLexer(lexer.RegexLexer):
929 Basic cabal lexer, does not try to be smart
931 name = 'Cabal'
932 aliases = ['cabal']
933 filenames = ['.cabal']
934 flags = re.MULTILINE
936 tokens = {
937 'root' : [
938 (r'^(\s*)(--.*)$', lexer.bygroups(token.Whitespace, token.Comment.Single)),
939 # key: value
940 (r'^(\s*)([\w\-_]+)(:)',
941 lexer.bygroups(token.Whitespace, token.Keyword, token.Punctuation)),
942 (r'^([\w\-_]+)', token.Keyword), # library, executable, flag etc.
943 (r'[^\S\n]+', token.Text),
944 (r'&&|\|\||==|<=|\^>=|>=|<|>', token.Operator),
945 (r',|:|{|}', token.Punctuation),
946 (r'.', token.Text)
950 def setup(app):
951 app.add_domain(CabalDomain)
952 app.add_lexer('cabal', CabalLexer)