Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --parser option to specify import path #60

Merged
merged 9 commits into from
Jun 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Changelog

- ``InterSphinxParser`` is now open to extension.
`#59 <https://github.com/hynek/doc2dash/pull/59>`_
- Support a ``--parser`` option to force the use of a custom parser class.
`#60 <https://github.com/hynek/doc2dash/pull/60>`_


----
Expand Down
6 changes: 6 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ Valid ``OPTIONS`` are the following:
To enable this, you must set this value to the base URL of your online documentation.
e.g. ``https://doc2dash.readthedocs.org/``

.. option:: --parser

Specify a the import path of a custom parser class (implementing the ``doc2dash.parsers.utils.IParser`` interface) to use.
For example, ``--parser doc2dash.parsers.intersphinx.InterSphinxParser`` will use the default ``InterSphinxParser``.
Default behavior is to auto-detect documentation type.

.. option:: -q, --quiet

Limit output to errors and warnings.
Expand Down
51 changes: 41 additions & 10 deletions src/doc2dash/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import plistlib
import shutil
import sqlite3
import importlib

import attr
import click
Expand Down Expand Up @@ -40,6 +41,29 @@ def emit(self, record):
), err=record.levelno >= logging.WARN)


class ImportableType(click.ParamType):
name = 'importable'

def convert(self, value, param, ctx):
path, dot, name = value.rpartition('.')

if not dot:
self.fail('%r is not an import path: does not contain "."' % value)

try:
mod = importlib.import_module(path)
except ImportError:
self.fail('Could not import module %r' % path)
try:
return getattr(mod, name)
except AttributeError:
self.fail('Failed to get attribute %r from module %r' %
(name, path))


IMPORTABLE = ImportableType()


@click.command()
@click.argument("source",
type=click.Path(exists=True, file_okay=False,
Expand Down Expand Up @@ -86,10 +110,16 @@ def emit(self, record):
"--online-redirect-url", "-u", default=None,
help="The base URL of the online documentation."
)
@click.option(
"--parser", default=None, type=IMPORTABLE,
help="The import path of a parser class (e.g. "
"doc2dash.parsers.intersphinx.InterSphinxParser). Default behavior "
"is to auto-detect documentation type."
)
@click.version_option(version=__version__)
def main(source, force, name, quiet, verbose, destination, add_to_dash,
add_to_global, icon, index_page, enable_js,
online_redirect_url):
online_redirect_url, parser):
"""
Convert docs from SOURCE to Dash.app's docset format.
"""
Expand All @@ -114,19 +144,20 @@ def main(source, force, name, quiet, verbose, destination, add_to_dash,
source, destination, name=name, add_to_global=add_to_global,
force=force
)
dt = parsers.get_doctype(source)
if dt is None:
log.error(
u'"{}" does not contain a known documentation format.'
.format(click.format_filename(source))
)
raise SystemExit(errno.EINVAL)
if parser is None:
parser = parsers.get_doctype(source)
if parser is None:
log.error(
u'"{}" does not contain a known documentation format.'
.format(click.format_filename(source))
)
raise SystemExit(errno.EINVAL)
docset = prepare_docset(source, dest, name, index_page, enable_js,
online_redirect_url)
doc_parser = dt(doc_path=docset.docs)
doc_parser = parser(doc_path=docset.docs)
log.info((u'Converting ' + click.style('{parser_name}', bold=True) +
u' docs from "{src}" to "{dst}".')
.format(parser_name=dt.name,
.format(parser_name=parser.name,
src=click.format_filename(source, shorten=True),
dst=click.format_filename(dest)))

Expand Down
53 changes: 45 additions & 8 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import plistlib
import shutil
import sqlite3
import sys

import attr
import pytest
Expand Down Expand Up @@ -101,20 +102,36 @@ def parse(self):
def find_and_patch_entry(self, soup, entry):
pass

class fake_module:
Parser = FakeParser

expected = '''\
Converting testtype docs from "foo" to "./{name}.docset".
Parsing documentation...
Added 1 index entries.
Adding table of contents meta data...
Adding to Dash.app...
'''

# alternative 1: use --parser
sys.modules['fake_module'] = fake_module
with patch("os.system") as system:
result = runner.invoke(
main.main, ["foo", "--parser", "fake_module.Parser", "-n", "bah",
"-a", "-i", str(png_file)]
)
assert expected.format(name='bah') == result.output
assert 0 == result.exit_code
assert ('open -a dash "./bah.docset"', ) == system.call_args[0]

# alternative 2: patch doc2dash.parsers
monkeypatch.setattr(doc2dash.parsers, "get_doctype", lambda _: FakeParser)
with patch("os.system") as system:
result = runner.invoke(
main.main, ["foo", "-n", "bar", "-a", "-i", str(png_file)]
)

assert expected.format(name='bar') == result.output
assert 0 == result.exit_code
assert '''\
Converting testtype docs from "foo" to "./bar.docset".
Parsing documentation...
Added 1 index entries.
Adding table of contents meta data...
Adding to Dash.app...
''' == result.output
assert ('open -a dash "./bar.docset"', ) == system.call_args[0]

# Again, just without adding and icon.
Expand All @@ -125,6 +142,26 @@ def find_and_patch_entry(self, soup, entry):

assert 0 == result.exit_code

# some tests for --parser validation

result = runner.invoke(main.main,
['foo', '-n', 'bing', '--parser', 'no_dot'])
assert "'no_dot' is not an import path" in result.output
assert 2 == result.exit_code

with patch("os.system") as system:
result = runner.invoke(main.main, ['foo', '-n', 'bing', '--parser',
'nonexistent_module.Parser'])
assert "Could not import module 'nonexistent_module'" in result.output
assert 2 == result.exit_code

with patch("os.system") as system:
result = runner.invoke(main.main, ['foo', '-n', 'bing', '--parser',
'sys.NonexistentParser'])
assert ("Failed to get attribute 'NonexistentParser' from module 'sys'"
in result.output)
assert 2 == result.exit_code


class TestSetupPaths(object):
def test_works(self, tmpdir):
Expand Down