Merge pull request #9898 from haskell/mergify/bp/3.12/pr-9865
[cabal.git] / doc / cabaldomain.py
blob2d318f8508ff967742579fcba21af540ea2303d2
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(CabalSection):
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 class ConfigField(CabalField):
560 section_key = 'cabal:cfg-section'
561 indextemplate = '%s ; cabal project option'
562 def handle_signature(self, sig, signode):
563 sig = sig.strip()
564 if sig.startswith('-'):
565 name = parse_flag(self, sig, signode)
566 else:
567 name = super(ConfigField, self).handle_signature(sig, signode)
569 return name
571 def get_index_entry(self, env, name):
572 if name.startswith('-'):
573 section = self.env.ref_context.get(self.section_key)
574 if section is not None:
575 parts = ('cfg-flag', section, name)
576 indexname = section + ':' + name
577 else:
578 parts = ('cfg-flag', name)
579 indexname = name
580 indexentry = name + '; cabal project option'
581 targetname = '-'.join(parts)
582 return indexentry, targetname
583 else:
584 return super(ConfigField,self).get_index_entry(env, name)
586 def get_env_key(self, env, name):
587 section = self.env.ref_context.get(self.section_key)
588 if name.startswith('-'):
589 return (section, name), 'cfg-flags'
590 return (section, name), 'cfg-fields'
592 class CabalConfigFieldXRef(CabalFieldXRef):
593 section_key = 'cabal:cfg-section'
597 # Cabal domain
600 class ConfigFieldIndex(Index):
601 name = 'syntax-quicklinks'
602 localname = "Cabal Syntax Quicklinks"
603 shortname = "Quicklinks"
605 class Entry(object):
606 def __init__(self, typ, name, doc, anchor, meta):
607 self.type = typ
608 self.name = name
609 self.doc = doc
610 self.anchor = anchor
611 self.meta = meta
613 def _gather_data(self, obj_types):
615 Gather objects and return [(title, [Entry])]
617 def massage(typ, datum):
618 name, (doc, anchor, meta) = datum
619 return self.Entry(typ, name, doc, anchor, meta)
621 fields = []
622 for typ in obj_types:
623 store = CabalDomain.types[typ]
624 fields += [massage(typ, x)
625 for x in self.domain.data[store].items()]
627 fields.sort(key=lambda x: (x.doc, x.meta.index))
629 if len(fields) == 0:
630 return []
632 result = []
633 current = []
634 current_title = fields[0].meta.title
635 for field in fields:
636 if field.meta.title != current_title:
637 result.append((current_title, current))
638 current = []
639 current_title = field.meta.title
640 current.append(field)
641 result.append((current_title, current))
643 return result
646 def generate(self, docnames=None):
648 Try to group entries such that if entry has a section then put it
649 into same group.
651 Otherwise group it under same `title`.
653 Try to keep in same order as it was defined.
655 sort by (document, index)
656 group on (document, doc_section)
658 TODO: Check how to extract section numbers from (document,doc_section)
659 and add it as annotation to titles
662 # (title, section store, fields store)
663 entries = [('cabal.project fields', 'cfg-section', 'cfg-field'),
664 ('cabal project flags', 'cfg-section', 'cfg-flag'),
665 ('package.cabal fields', 'pkg-section', 'pkg-field')]
667 result = []
668 for label, section_key, key in entries:
670 data = self._gather_data([section_key, key])
672 references = []
673 for section, entries in data:
674 if section is None:
675 elem_type = 0 # Normal entry
676 else:
677 elem_type = 2 # sub_entry
679 assert len(entries) != 0
680 docname = entries[0].doc
681 if section is not None:
682 section_title, section_anchor = section
683 references.append(
684 (section_title, 1, docname, section_anchor, '', '', ''))
686 for entry in entries:
687 #todo deal with if
688 if isinstance(entry.name, tuple):
689 name = entry.name[1]
690 else:
691 name = entry.name
693 meta = entry.meta
694 extra = render_meta(meta)
695 descr = meta.synopsis if meta.synopsis is not None else ''
696 field = (name, elem_type, docname,
697 entry.anchor, extra, '', descr)
698 references.append(field)
699 result.append((label, references))
701 return result, False
703 def make_data_keys(typ, target, node):
705 Returns a list of keys to search for targets of this type
706 in domain data.
708 Used for resolving references
710 if typ == 'pkg-field':
711 section = node.get('cabal:pkg-section')
712 return [(section, target),
713 (None, target)]
714 elif typ in ('cfg-field', 'cfg-flag'):
715 section = node.get('cabal:cfg-section')
716 return [(section, target), (None, target)]
717 else:
718 return [target]
721 def render_deprecated(deprecated):
722 if isinstance(deprecated, StrictVersion):
723 return 'deprecated since: '+str(deprecated)
724 else:
725 return 'deprecated'
727 def render_removed(deprecated, removed):
728 if isinstance(deprecated, StrictVersion):
729 return 'removed in: ' + str(removed) + '; deprecated since: '+str(deprecated)
730 else:
731 return 'removed in: ' + str(removed)
733 def render_meta(meta):
735 Render meta as short text
737 Will render either deprecated or since info
739 if meta.removed is not None:
740 return render_removed(meta.deprecated, meta.removed)
741 if meta.deprecated is not None:
742 return render_deprecated(meta.deprecated)
743 elif meta.since is not None:
744 return 'since version: ' + str(meta.since)
745 else:
746 return ''
748 def render_meta_title(meta):
750 Render meta as suitable to use in titles
752 rendered = render_meta(meta)
753 if rendered != '':
754 return '(' + rendered + ')'
755 return ''
757 def make_title(typ, key, meta):
759 Render title of an object (section, field or flag)
761 if typ == 'pkg-section':
762 return "package.cabal " + key + " section " + render_meta_title(meta)
764 elif typ == 'pkg-field':
765 section, name = key
766 if section is not None:
767 base = "package.cabal " + section + " section " + name + ": field"
768 else:
769 base = "package.cabal " + name + " field"
771 return base + render_meta_title(meta)
773 elif typ == 'cfg-section':
774 return "cabal.project " + key + " section " + render_meta_title(meta)
776 elif typ == 'cfg-field':
777 section, name = key
778 return "cabal.project " + name + " field " + render_meta_title(meta)
780 elif typ == 'cfg-flag':
781 section, name = key
782 return "cabal flag " + name + " " + render_meta_title(meta)
784 else:
785 raise ValueError("Unknown type: " + typ)
787 def make_full_name(typ, key, meta):
789 Return an anchor name for object type
791 if typ == 'pkg-section':
792 return 'pkg-section-' + key
794 elif typ == 'pkg-field':
795 section, name = key
796 if section is not None:
797 return '-'.join(('pkg-field',section, name))
798 else:
799 return 'pkg-field-' + name
801 elif typ == 'cfg-field':
802 return 'cfg-field-' + key
804 else:
805 raise ValueError('Unknown object type: ' + typ)
807 class CabalDomain(Domain):
809 Sphinx domain for cabal
811 needs Domain.merge_doc for parallel building, just union all dicts
813 name = 'cabal'
814 label = 'Cabal'
815 object_types = {
816 'pkg-section': ObjType(_('pkg-section'), 'pkg-section'),
817 'pkg-field' : ObjType(_('pkg-field') , 'pkg-field' ),
818 'cfg-section': ObjType(_('cfg-section'), 'cfg-section'),
819 'cfg-field' : ObjType(_('cfg-field') , 'cfg-field' ),
821 directives = {
822 'pkg-section': CabalPackageSection,
823 'pkg-field' : CabalPackageField,
824 'cfg-section': CabalConfigSection,
825 'cfg-field' : ConfigField,
827 roles = {
828 'pkg-section': XRefRole(warn_dangling=True),
829 'pkg-field' : CabalPackageFieldXRef(warn_dangling=True),
830 'cfg-section': XRefRole(warn_dangling=True),
831 'cfg-field' : CabalConfigFieldXRef(warn_dangling=True),
832 'cfg-flag' : CabalConfigFieldXRef(warn_dangling=True),
834 initial_data = {
835 'pkg-sections': {},
836 'pkg-fields' : {},
837 'cfg-sections': {},
838 'index-num' : {}, #per document number of objects
839 # used to order references page
840 'cfg-fields' : {},
841 'cfg-flags' : {},
843 indices = [
844 ConfigFieldIndex
846 types = {
847 'pkg-section': 'pkg-sections',
848 'pkg-field' : 'pkg-fields',
849 'cfg-section': 'cfg-sections',
850 'cfg-field' : 'cfg-fields',
851 'cfg-flag' : 'cfg-flags',
853 def clear_doc(self, docname):
854 for k in ['pkg-sections', 'pkg-fields', 'cfg-sections',
855 'cfg-fields', 'cfg-flags']:
856 to_del = []
857 for name, (fn, _, _) in self.data[k].items():
858 if fn == docname:
859 to_del.append(name)
860 for name in to_del:
861 del self.data[k][name]
862 try:
863 del self.data['index-num'][docname]
864 except KeyError:
865 pass
867 def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode):
868 objtypes = self.objtypes_for_role(type)
869 for typ, key in ((typ, key)
870 for typ in objtypes
871 for key in make_data_keys(typ, target, node)):
872 try:
873 data = env.domaindata['cabal'][self.types[typ]][key]
874 except KeyError:
875 continue
876 doc, ref, meta = data
877 title = make_title(typ, key, meta)
878 return make_refnode(builder, fromdocname, doc, ref, contnode, title)
880 def get_objects(self):
882 Used for search functionality
884 for typ in ['pkg-section', 'pkg-field',
885 'cfg-section', 'cfg-field', 'cfg-flag']:
886 key = self.types[typ]
887 for name, (fn, target, meta) in self.data[key].items():
888 title = make_title(typ, name, meta)
889 yield title, title, typ, fn, target, 0
891 class CabalLexer(lexer.RegexLexer):
893 Basic cabal lexer, does not try to be smart
895 name = 'Cabal'
896 aliases = ['cabal']
897 filenames = ['.cabal']
898 flags = re.MULTILINE
900 tokens = {
901 'root' : [
902 (r'^(\s*)(--.*)$', lexer.bygroups(token.Whitespace, token.Comment.Single)),
903 # key: value
904 (r'^(\s*)([\w\-_]+)(:)',
905 lexer.bygroups(token.Whitespace, token.Keyword, token.Punctuation)),
906 (r'^([\w\-_]+)', token.Keyword), # library, executable, flag etc.
907 (r'[^\S\n]+', token.Text),
908 (r'&&|\|\||==|<=|\^>=|>=|<|>', token.Operator),
909 (r',|:|{|}', token.Punctuation),
910 (r'.', token.Text)
914 def setup(app):
915 app.add_domain(CabalDomain)
916 app.add_lexer('cabal', CabalLexer)