Skip to content

Commit

Permalink
Accept linksmith inventory without INFILES argument
Browse files Browse the repository at this point in the history
This implements auto-discovery of `objects.inv` in local current working
directory.
  • Loading branch information
amotl committed Apr 5, 2024
1 parent dcb5a3b commit 8576d53
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
- Implement `linksmith anansi suggest`, also available as `anansi`,
to easily suggest terms of a few curated community projects.
Thanks, @bskinn.
- Accept `linksmith inventory` without `INFILES` argument, implementing
auto-discovery of `objects.inv` in local current working directory.
8 changes: 8 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ linksmith inventory \
```


:::{rubric} Auto-Discovery
:::
Discover `objects.inv` in working directory.
```shell
linksmith inventory
```


## Anansi

Suggest references from curated intersphinx inventories.
Expand Down
31 changes: 15 additions & 16 deletions linksmith/sphinx/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import logging
import typing as t

import rich_click as click
from click import ClickException

from linksmith.settings import help_config
from linksmith.sphinx.core import inventories_to_text, inventory_to_text
from linksmith.sphinx.core import dump_inventory_universal

logger = logging.getLogger(__name__)


@click.command()
Expand All @@ -30,18 +32,15 @@ def cli(ctx: click.Context, infiles: t.List[str], format_: str):
```bash
linksmith inventory https://github.com/crate/crate-docs/raw/main/registry/sphinx-inventories.txt
```
Discover `objects.inv` in working directory:
```bash
linksmith inventory
```
"""
if not infiles:
raise click.ClickException("No input")
for infile in infiles:
try:
if infile.endswith(".inv"):
inventory_to_text(infile, format_=format_)
elif infile.endswith(".txt"):
inventories_to_text(infile, format_=format_)
else:
raise NotImplementedError(f"Unknown input file type: {infile}")
except Exception as ex:
if ctx.parent and ctx.parent.params.get("debug"):
raise
raise ClickException(f"{ex.__class__.__name__}: {ex}")
try:
dump_inventory_universal(infiles, format_)
except Exception as ex:
if ctx.parent and ctx.parent.params.get("debug"):
raise
raise click.ClickException(str(ex))
22 changes: 22 additions & 0 deletions linksmith/sphinx/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,32 @@

from linksmith.model import OutputFormat, OutputFormatRegistry, ResourceType
from linksmith.sphinx.inventory import InventoryFormatter
from linksmith.sphinx.util import LocalObjectsInv

logger = logging.getLogger(__name__)


def dump_inventory_universal(infiles: t.List[str], format_: str = "text"):
"""
Decode one or multiple intersphinx inventories and output in different formats.
"""
if not infiles:
logger.info("No inventory specified, entering auto-discovery mode")
try:
local_objects_inv = LocalObjectsInv.discover(Path.cwd())
logger.info(f"Auto-discovered objects.inv: {local_objects_inv}")
infiles = [str(local_objects_inv)]
except Exception as ex:
raise FileNotFoundError(f"No inventory specified, and none discovered: {ex}")
for infile in infiles:
if infile.endswith(".inv"):
inventory_to_text(infile, format_=format_)
elif infile.endswith(".txt"):
inventories_to_text(infile, format_=format_)
else:
raise NotImplementedError(f"Unknown input file type: {infile}")


def inventory_to_text(url: str, format_: str = "text"):
"""
Display intersphinx inventory for individual project, using selected output format.
Expand Down
29 changes: 29 additions & 0 deletions linksmith/sphinx/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pathlib import Path


class LocalObjectsInv:
"""
Support discovering an `objects.inv` in current working directory.
"""

# Candidate paths where to look for `objects.inv` in current working directory.
objects_inv_candidates = [
Path("objects.inv"),
Path("doc") / "_build" / "objects.inv",
Path("docs") / "_build" / "objects.inv",
Path("doc") / "_build" / "html" / "objects.inv",
Path("docs") / "_build" / "html" / "objects.inv",
Path("doc") / "build" / "html" / "objects.inv",
Path("docs") / "build" / "html" / "objects.inv",
]

@classmethod
def discover(cls, project_root: Path) -> Path:
"""
Return `Path` instance of discovered `objects.inv` in current working directory.
"""
for candidate in cls.objects_inv_candidates:
path = project_root / candidate
if path.exists():
return path
raise FileNotFoundError("No objects.inv found in working directory")
13 changes: 0 additions & 13 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,6 @@ def test_cli_output_formats(cli_runner):
assert result.exit_code == 0


def test_cli_inventory_no_input(cli_runner):
"""
CLI test: Invoke `linksmith inventory`.
"""
result = cli_runner.invoke(
cli,
args="inventory",
catch_exceptions=False,
)
assert result.exit_code == 1
assert "No input" in result.output


def test_cli_inventory_unknown_input(cli_runner):
"""
CLI test: Invoke `linksmith inventory example.foo`.
Expand Down
24 changes: 24 additions & 0 deletions tests/test_inventory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from unittest.mock import patch

import pytest

from linksmith.sphinx.core import dump_inventory_universal
from linksmith.sphinx.inventory import InventoryFormatter, InventoryManager
from tests.config import OBJECTS_INV_PATH

Expand All @@ -25,3 +28,24 @@ def test_inventory_manager_unknown():
with pytest.raises(NotImplementedError) as ex:
invman.soi_factory()
assert ex.match("Resource type not implemented: foo")


def test_cli_inventory_autodiscover(capsys):
"""
Verify local `objects.inv` auto-discovery works.
"""
with patch("linksmith.sphinx.util.LocalObjectsInv.objects_inv_candidates", ["tests/assets/linksmith.inv"]):
dump_inventory_universal([])
out, err = capsys.readouterr()
assert "std:doc" in out
assert "std:label" in out


def test_inventory_no_input():
"""
Exercise a failing auto-discovery, where absolutely no input files can be determined.
"""
with patch("linksmith.sphinx.util.LocalObjectsInv.objects_inv_candidates", []):
with pytest.raises(FileNotFoundError) as ex:
dump_inventory_universal([])
ex.match("No inventory specified, and none discovered: No objects.inv found in working directory")

0 comments on commit 8576d53

Please sign in to comment.