Skip to content

Commit

Permalink
Make InterSphinxParser extensible (#59)
Browse files Browse the repository at this point in the history
* Make InterSphinxParser extensible

* allow INV_TO_TYPE mapping to be overridden via create_type method

* allow ParserEntry creation to be overridden with create_entry method

* PEP8

* Allow create_entry to return None

* Add changelog entry and minor style tweaks

* Minor style
  • Loading branch information
jnothman authored and hynek committed Jun 3, 2017
1 parent 589fab4 commit dd6306e
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 23 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Changelog
2.2.0 (UNRELEASED)
------------------

No changes yet.
- ``InterSphinxParser`` is now open to extension.
`#59 <https://github.com/hynek/doc2dash/pull/59>`_


----
Expand Down
66 changes: 46 additions & 20 deletions src/doc2dash/parsers/intersphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,45 @@ def parse(self):
"""
with open(os.path.join(self.doc_path, "objects.inv"), "rb") as inv_f:
inv_f.readline() # skip version line that is verified in detection
for pe in _inv_to_entries(
for pe in self._inv_to_entries(
read_inventory_v2(inv_f, "", os.path.join)
): # this is what Guido gave us `yield from` for :-|
yield pe

def _inv_to_entries(self, inv):
"""
Iterate over a dictionary as returned from Sphinx's object.inv parser
and yield `ParserEntry`s.
"""
for type_key, inv_entries in iteritems(inv):
dash_type = self.convert_type(type_key)
if dash_type is None:
continue
for key, data in iteritems(inv_entries):
entry = self.create_entry(dash_type, key, data)
if entry is not None:
yield entry

def convert_type(self, inv_type):
"""
Map an intersphinx type to a Dash type.
Returns a Dash type string, or None to not construct entries.
"""
try:
return INV_TO_TYPE[inv_type.split(":")[-1]]
except KeyError:
return

def create_entry(self, dash_type, key, inv_entry):
"""
Create a ParserEntry (or None) given inventory details
Parameters are the dash type, intersphinx inventory key and data tuple
"""
path_str = inv_entry_to_path(inv_entry)
return ParserEntry(name=key, type=dash_type, path=path_str)

def find_and_patch_entry(self, soup, entry): # pragma: nocover
return find_and_patch_entry(soup, entry)

Expand All @@ -95,24 +129,16 @@ def find_and_patch_entry(soup, entry):
return False


def _inv_to_entries(inv):
def inv_entry_to_path(data):
"""
Iterate over a dictionary as returned from Sphinx's object.inv parser and
yield `ParserEntry`s.
Determine the path from the intersphinx inventory entry
Discard the anchors between head and tail to make it
compatible with situations where extra meta information is encoded.
"""
for type_key, val in iteritems(inv):
try:
t = INV_TO_TYPE[type_key.split(":")[-1]]
for el, data in iteritems(val):
"""
Discard the anchors between head and tail to make it
compatible with situations that extra meta information encoded
"""
path_tuple = data[2].split("#")
if len(path_tuple) > 1:
path_str = "#".join((path_tuple[0], path_tuple[-1]))
else:
path_str = data[2]
yield ParserEntry(name=el, type=t, path=path_str)
except KeyError:
pass
path_tuple = data[2].split("#")
if len(path_tuple) > 1:
path_str = "#".join((path_tuple[0], path_tuple[-1]))
else:
path_str = data[2]
return path_str
79 changes: 77 additions & 2 deletions tests/parsers/intersphinx/test_intersphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from doc2dash.parsers.intersphinx import (
InterSphinxParser,
find_and_patch_entry,
_inv_to_entries,
inv_entry_to_path,
)
from doc2dash.parsers.utils import IParser, ParserEntry, TOCEntry

Expand All @@ -36,8 +36,9 @@ def test_inv_to_entries(self):
"""
Inventory items are correctly converted.
"""
p = InterSphinxParser(doc_path=os.path.join(HERE))
result = list(
_inv_to_entries({"py:method": {
p._inv_to_entries({"py:method": {
u"some_method": (None, None, u"some_module.py", u"-"),
}, "std:option": {
u"--destination": (
Expand Down Expand Up @@ -65,6 +66,80 @@ def test_inv_to_entries(self):
)
]) == set(result)

def test_convert_type_override(self):
"""
`convert_type` can be overridden.
We check that we can hide some key of choice.
"""
class MyInterSphinxParser(InterSphinxParser):
def convert_type(self, inv_type):
if inv_type == 'py:method':
# hide method entries
return
return super(MyInterSphinxParser, self).convert_type(inv_type)

p = MyInterSphinxParser(doc_path=os.path.join(HERE))
result = list(
p._inv_to_entries({"py:method": {
u"some_method": (None, None, u"some_module.py", u"-"),
}, "std:constant": {
u"SomeConstant": (None, None, u"some_other_module.py", u"-")
}})
)
assert [ParserEntry(
name=u'SomeConstant',
type=u'Constant',
path=u'some_other_module.py'
)] == result

def test_create_entry_override(self):
"""
`create_entry` has the expected interface and can be overridden.
We check that the name format can be adjusted.
"""
class MyInterSphinxParser(InterSphinxParser):
def create_entry(self, dash_type, key, inv_entry):
path_str = inv_entry_to_path(inv_entry)
return ParserEntry(name='!%s!' % key, type=dash_type,
path=path_str)

p = MyInterSphinxParser(doc_path=os.path.join(HERE))
result = list(
p._inv_to_entries({"py:method": {
u"some_method": (None, None, u"some_module.py", u"-"),
}}))
assert [ParserEntry(name=u'!some_method!', type=u'Method',
path=u'some_module.py')] == result

def test_create_entry_none(self):
"""
`create_entry` can return None.
"""
class MyInterSphinxParser(InterSphinxParser):
def create_entry(self, dash_type, key, inv_entry):
if dash_type == 'Option':
return
return super(MyInterSphinxParser, self).create_entry(
dash_type, key, inv_entry)

p = MyInterSphinxParser(doc_path=os.path.join(HERE))
result = list(
p._inv_to_entries({"py:method": {
u"some_method": (None, None, u"some_module.py", u"-"),
}, "std:option": {
u"--destination": (
u"doc2dash",
u"2.0",
u"index.html#document-usage#cmdoption--destination",
u"-"
)
}})
)
assert [ParserEntry(name=u'some_method', type=u'Method',
path=u'some_module.py')] == result


class TestFindAndPatchEntry(object):
def test_patch_method(self):
Expand Down

0 comments on commit dd6306e

Please sign in to comment.