Skip to content

Commit 8402b70

Browse files
committed
Merge pull request #89 from google/sections
Improve section/order handling
2 parents 301b139 + cc6dd0e commit 8402b70

File tree

5 files changed

+168
-33
lines changed

5 files changed

+168
-33
lines changed

tests/__init__.py

Whitespace-only changes.

tests/module_tests.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import unittest
2+
3+
import vimdoc
4+
from vimdoc.block import Block
5+
from vimdoc import error
6+
from vimdoc import module
7+
8+
class TestVimModule(unittest.TestCase):
9+
10+
def test_section(self):
11+
plugin = module.VimPlugin('myplugin')
12+
main_module = module.Module('myplugin', plugin)
13+
intro = Block(vimdoc.SECTION)
14+
intro.Local(name='Introduction', id='intro')
15+
main_module.Merge(intro)
16+
main_module.Close()
17+
self.assertEqual([intro], list(main_module.Chunks()))
18+
19+
def test_duplicate_section(self):
20+
plugin = module.VimPlugin('myplugin')
21+
main_module = module.Module('myplugin', plugin)
22+
intro = Block(vimdoc.SECTION)
23+
intro.Local(name='Introduction', id='intro')
24+
main_module.Merge(intro)
25+
intro2 = Block(vimdoc.SECTION)
26+
intro2.Local(name='Intro', id='intro')
27+
with self.assertRaises(error.DuplicateSection) as cm:
28+
main_module.Merge(intro2)
29+
self.assertEqual(('Duplicate section intro defined.',), cm.exception.args)
30+
31+
def test_default_section_ordering(self):
32+
"""Sections should be ordered according to documented built-in ordering."""
33+
plugin = module.VimPlugin('myplugin')
34+
main_module = module.Module('myplugin', plugin)
35+
intro = Block(vimdoc.SECTION)
36+
intro.Local(name='Introduction', id='intro')
37+
commands = Block(vimdoc.SECTION)
38+
commands.Local(name='Commands', id='commands')
39+
about = Block(vimdoc.SECTION)
40+
about.Local(name='About', id='about')
41+
# Merge in arbitrary order.
42+
main_module.Merge(commands)
43+
main_module.Merge(about)
44+
main_module.Merge(intro)
45+
main_module.Close()
46+
self.assertEqual([intro, commands, about], list(main_module.Chunks()))
47+
48+
def test_manual_section_ordering(self):
49+
"""Sections should be ordered according to explicitly configured order."""
50+
plugin = module.VimPlugin('myplugin')
51+
main_module = module.Module('myplugin', plugin)
52+
intro = Block(vimdoc.SECTION)
53+
intro.Local(name='Introduction', id='intro')
54+
# Configure explicit order.
55+
intro.Global(order=['commands', 'about', 'intro'])
56+
commands = Block(vimdoc.SECTION)
57+
commands.Local(name='Commands', id='commands')
58+
about = Block(vimdoc.SECTION)
59+
about.Local(name='About', id='about')
60+
# Merge in arbitrary order.
61+
main_module.Merge(commands)
62+
main_module.Merge(about)
63+
main_module.Merge(intro)
64+
main_module.Close()
65+
self.assertEqual([commands, about, intro], list(main_module.Chunks()))
66+
67+
def test_partial_ordering(self):
68+
"""Always respect explicit order and prefer built-in ordering.
69+
70+
Undeclared built-in sections will be inserted into explicit order according
71+
to default built-in ordering. The about section should come after custom
72+
sections unless explicitly ordered."""
73+
plugin = module.VimPlugin('myplugin')
74+
main_module = module.Module('myplugin', plugin)
75+
intro = Block(vimdoc.SECTION)
76+
intro.Local(name='Introduction', id='intro')
77+
# Configure explicit order.
78+
intro.Global(order=['custom1', 'intro', 'custom2'])
79+
commands = Block(vimdoc.SECTION)
80+
commands.Local(name='Commands', id='commands')
81+
about = Block(vimdoc.SECTION)
82+
about.Local(name='About', id='about')
83+
custom1 = Block(vimdoc.SECTION)
84+
custom1.Local(name='Custom1', id='custom1')
85+
custom2 = Block(vimdoc.SECTION)
86+
custom2.Local(name='Custom2', id='custom2')
87+
# Merge in arbitrary order.
88+
for section in [commands, custom2, about, intro, custom1]:
89+
main_module.Merge(section)
90+
main_module.Close()
91+
self.assertEqual([custom1, intro, commands, custom2, about],
92+
list(main_module.Chunks()))

vimdoc/block.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ class Block(object):
1616
contain metadata statements specifying things like the plugin author, etc.
1717
1818
Args:
19+
type: Block type, e.g. vim.SECTION or vim.FUNCTION.
1920
is_secondary: Whether there are other blocks above this one that describe
2021
the same item. Only primary blocks should have tags, not secondary
2122
blocks.
2223
is_default: Whether other blocks with the same type and tag should override
2324
this one and prevent this block from showing up in the docs.
2425
"""
2526

26-
def __init__(self, is_secondary=False, is_default=False):
27+
def __init__(self, type=None, is_secondary=False, is_default=False):
2728
# May include:
2829
# deprecated (boolean)
2930
# dict (name)
@@ -34,6 +35,8 @@ def __init__(self, is_secondary=False, is_default=False):
3435
# namespace (of function)
3536
# attribute (of function in dict)
3637
self.locals = {}
38+
if type is not None:
39+
self.SetType(type)
3740
# Merged into module. May include:
3841
# author (string)
3942
# library (boolean)

vimdoc/error.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ def __init__(self, section):
116116
'Section {} never defined.'.format(section))
117117

118118

119+
class DuplicateSection(BadStructure):
120+
def __init__(self, section):
121+
super(DuplicateSection, self).__init__(
122+
'Duplicate section {} defined.'.format(section))
123+
124+
125+
class DuplicateBackmatter(BadStructure):
126+
def __init__(self, section):
127+
super(DuplicateBackmatter, self).__init__(
128+
'Duplicate backmatter defined for section {}.'.format(section))
129+
130+
119131
class NeglectedSections(BadStructure):
120132
def __init__(self, sections, order):
121133
super(NeglectedSections, self).__init__(

vimdoc/module.py

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,17 @@ def Merge(self, block, namespace=None):
6060
# Overwrite existing section if it's a default.
6161
if block_id not in self.sections or self.sections[block_id].IsDefault():
6262
self.sections[block_id] = block
63+
elif not block.IsDefault():
64+
# Tried to overwrite explicit section with explicit section.
65+
raise error.DuplicateSection(block_id)
6366
elif typ == vimdoc.BACKMATTER:
6467
# Overwrite existing section backmatter if it's a default.
6568
if (block_id not in self.backmatters
6669
or self.backmatters[block_id].IsDefault()):
6770
self.backmatters[block_id] = block
71+
elif not block.IsDefault():
72+
# Tried to overwrite explicit backmatter with explicit backmatter.
73+
raise error.DuplicateBackmatter(block_id)
6874
else:
6975
collection_type = self.plugin.GetCollectionType(block)
7076
if collection_type is not None:
@@ -107,31 +113,26 @@ def Close(self):
107113
All default sections that have not been overridden will be created.
108114
"""
109115
if self.GetCollection(vimdoc.FUNCTION) and 'functions' not in self.sections:
110-
functions = Block()
111-
functions.SetType(vimdoc.SECTION)
116+
functions = Block(vimdoc.SECTION)
112117
functions.Local(id='functions', name='Functions')
113118
self.Merge(functions)
114119
if (self.GetCollection(vimdoc.EXCEPTION)
115120
and 'exceptions' not in self.sections):
116-
exceptions = Block()
117-
exceptions.SetType(vimdoc.SECTION)
121+
exceptions = Block(vimdoc.SECTION)
118122
exceptions.Local(id='exceptions', name='Exceptions')
119123
self.Merge(exceptions)
120124
if self.GetCollection(vimdoc.COMMAND) and 'commands' not in self.sections:
121-
commands = Block()
122-
commands.SetType(vimdoc.SECTION)
125+
commands = Block(vimdoc.SECTION)
123126
commands.Local(id='commands', name='Commands')
124127
self.Merge(commands)
125128
if self.GetCollection(vimdoc.DICTIONARY) and 'dicts' not in self.sections:
126-
dicts = Block()
127-
dicts.SetType(vimdoc.SECTION)
129+
dicts = Block(vimdoc.SECTION)
128130
dicts.Local(id='dicts', name='Dictionaries')
129131
self.Merge(dicts)
130132
if self.GetCollection(vimdoc.FLAG):
131133
# If any maktaba flags were documented, add a default configuration
132134
# section to explain how to use them.
133-
config = Block(is_default=True)
134-
config.SetType(vimdoc.SECTION)
135+
config = Block(vimdoc.SECTION, is_default=True)
135136
config.Local(id='config', name='Configuration')
136137
config.AddLine(
137138
'This plugin uses maktaba flags for configuration. Install Glaive'
@@ -141,29 +142,18 @@ def Close(self):
141142
if ((self.GetCollection(vimdoc.FLAG) or
142143
self.GetCollection(vimdoc.SETTING)) and
143144
'config' not in self.sections):
144-
config = Block()
145-
config.SetType(vimdoc.SECTION)
145+
config = Block(vimdoc.SECTION)
146146
config.Local(id='config', name='Configuration')
147147
self.Merge(config)
148-
if not self.order:
149-
self.order = []
150-
for builtin in [
151-
'intro',
152-
'config',
153-
'commands',
154-
'autocmds',
155-
'settings',
156-
'dicts',
157-
'functions',
158-
'exceptions',
159-
'mappings',
160-
'about']:
161-
if builtin in self.sections or builtin in self.backmatters:
162-
self.order.append(builtin)
148+
163149
for backmatter in self.backmatters:
164150
if backmatter not in self.sections:
165151
raise error.NoSuchSection(backmatter)
166-
known = set(self.sections) | set(self.backmatters)
152+
# Use explicit order as partial ordering and merge with default section
153+
# ordering. All custom sections must be ordered explicitly.
154+
self.order = self._GetSectionOrder(self.order, self.sections)
155+
156+
known = set(self.sections)
167157
neglected = sorted(known.difference(self.order))
168158
if neglected:
169159
raise error.NeglectedSections(neglected, self.order)
@@ -200,6 +190,46 @@ def Chunks(self):
200190
if ident in self.backmatters:
201191
yield self.backmatters[ident]
202192

193+
@staticmethod
194+
def _GetSectionOrder(explicit_order, sections):
195+
"""Gets final section order from explicit_order and actual sections present.
196+
197+
Built-in sections with no explicit order come before custom sections, with
198+
two exceptions:
199+
* The "about" section comes last by default.
200+
* If a built-in section is explicitly ordered, it "resets" the ordering so
201+
so that subsequent built-in sections come directly after it.
202+
This yields the order you would intuitively expect in cases like ordering
203+
"intro" after other sections.
204+
"""
205+
order = explicit_order or []
206+
default_order = [
207+
'intro',
208+
'config',
209+
'commands',
210+
'autocmds',
211+
'settings',
212+
'dicts',
213+
'functions',
214+
'exceptions',
215+
'mappings']
216+
# Add any undeclared sections before custom sections, except 'about' which
217+
# comes at the end by default.
218+
section_insertion_idx = 0
219+
order = order[:]
220+
for builtin in default_order:
221+
if builtin in order:
222+
# Section already present. Skip and continue later sections after it.
223+
section_insertion_idx = order.index(builtin) + 1
224+
continue
225+
else:
226+
# If section present, insert into order at logical index.
227+
if builtin in sections:
228+
order.insert(section_insertion_idx, builtin)
229+
section_insertion_idx += 1
230+
if 'about' in sections and 'about' not in order:
231+
order.append('about')
232+
return order
203233

204234
class VimPlugin(object):
205235
"""State for entire plugin (potentially multiple modules)."""
@@ -249,8 +279,7 @@ def LookupTag(self, typ, name):
249279
block = candidates[0]
250280
if block is None:
251281
# Create a dummy block to get default tag.
252-
block = Block()
253-
block.SetType(typ)
282+
block = Block(typ)
254283
block.Local(name=fullname)
255284
return block.TagName()
256285

@@ -353,8 +382,7 @@ def Modules(directory):
353382
flagpath = relative_path
354383
if flagpath.startswith('after' + os.path.sep):
355384
flagpath = os.path.relpath(flagpath, 'after')
356-
flagblock = Block(is_default=True)
357-
flagblock.SetType(vimdoc.FLAG)
385+
flagblock = Block(vimdoc.FLAG, is_default=True)
358386
name_parts = os.path.splitext(flagpath)[0].split(os.path.sep)
359387
flagname = name_parts.pop(0)
360388
flagname += ''.join('[' + p + ']' for p in name_parts)

0 commit comments

Comments
 (0)