Skip to content

Commit

Permalink
sphinx-agent: Update directive indexing to align with roles approach
Browse files Browse the repository at this point in the history
This also adds the Sphinx-side types needed to specify argument providers
  • Loading branch information
alcarney committed Jan 7, 2025
1 parent 51002f9 commit bc535de
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 18 deletions.
50 changes: 50 additions & 0 deletions lib/esbonio/esbonio/sphinx_agent/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@
from docutils.nodes import Element
from docutils.parsers.rst import Directive

DirectiveDefinition = tuple[
str, type[Directive], list[types.Directive.ArgumentProvider]
]
RoleDefinition = tuple[str, Any, list[types.Role.TargetProvider]]


sphinx_logger = logging.getLogger(SPHINX_LOG_NAMESPACE)
logger = sphinx_logger.getChild("esbonio")
sphinx_log_setup = sphinx_logging_module.setup
Expand Down Expand Up @@ -55,6 +59,9 @@ def __init__(self, dbpath: pathlib.Path, app: _Sphinx):
self._roles: list[RoleDefinition] = []
"""Roles captured during Sphinx startup."""

self._directives: list[DirectiveDefinition] = []
"""Directives captured during Sphinx startup."""

self._config_ast: ast.Module | Literal[False] | None = None
"""The parsed AST of the user's conf.py file.
If ``False``, we already tried parsing the module and were unable to."""
Expand Down Expand Up @@ -83,6 +90,48 @@ def config_ast(self) -> ast.Module | None:

return None

def add_directive(
self,
name: str,
directive: type[Directive],
argument_providers: list[types.Directive.ArgumentProvider] | None = None,
):
"""Register a directive with esbonio.
Parameters
----------
name
The name of the directive, as the user would type in a document
directive
The directive's implementation
argument_providers
A list of argument providers for the role
"""
self._directives.append((name, directive, argument_providers or []))

@staticmethod
def create_directive_argument_provider(
name: str, **kwargs
) -> types.Directive.ArgumentProvider:
"""Create a new directive argument provider
Parameters
----------
name
The name of the provider
kwargs
Additional arguments to pass to the provider instance
Returns
-------
types.Directive.ArgumentProvider
The target provider
"""
return types.Directive.ArgumentProvider(name, kwargs)

def add_role(
self,
name: str,
Expand Down Expand Up @@ -154,6 +203,7 @@ def add_role(self, name: str, role: Any, override: bool = False):

def add_directive(self, name: str, cls: type[Directive], override: bool = False):
super().add_directive(name, cls, override or self._esbonio_retry_count > 0)
self.esbonio.add_directive(name, cls)

def add_node(self, node: type[Element], override: bool = False, **kwargs):
super().add_node(node, override or self._esbonio_retry_count > 0, **kwargs)
Expand Down
31 changes: 15 additions & 16 deletions lib/esbonio/esbonio/sphinx_agent/handlers/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Database.Column(name="name", dtype="TEXT"),
Database.Column(name="implementation", dtype="TEXT"),
Database.Column(name="location", dtype="JSON"),
Database.Column(name="argument_providers", dtype="JSON"),
],
)

Expand Down Expand Up @@ -66,7 +67,13 @@ def index_directives(app: Sphinx):
first time.
"""

directives: list[types.Directive] = []
directives: dict[str, types.Directive] = {}

# Process the roles registered through Sphinx
for name, impl, providers in app.esbonio._directives:
directives[name] = types.Directive(
name, get_impl_name(impl), argument_providers=providers
)

ignored_directives = {"restructuredtext-test-directive"}
found_directives = {
Expand All @@ -75,7 +82,7 @@ def index_directives(app: Sphinx):
}

for name, directive in found_directives.items():
if name in ignored_directives:
if name in ignored_directives or name in directives:
continue

# core docutils directives are a (module, Class) reference.
Expand All @@ -88,25 +95,17 @@ def index_directives(app: Sphinx):
directive = getattr(module, cls)
except Exception:
# TODO: Log the error somewhere...
directives.append((name, None, None))
directives[name] = types.Directive(name, None, None)
continue

directives.append((name, get_impl_name(directive), None))

for prefix, domain in app.env.domains.items():
for name, directive in domain.directives.items():
directives.append(
(
f"{prefix}:{name}",
get_impl_name(directive),
None,
)
)
directives[name] = types.Directive(name, get_impl_name(directive))

app.esbonio.db.ensure_table(DIRECTIVES_TABLE)
app.esbonio.db.clear_table(DIRECTIVES_TABLE)
app.esbonio.db.insert_values(DIRECTIVES_TABLE, directives)
app.esbonio.db.insert_values(
DIRECTIVES_TABLE, [d.to_db(as_json) for d in directives.values()]
)


def setup(app: Sphinx):
app.connect("builder-inited", index_directives)
app.connect("builder-inited", index_directives, priority=999)
20 changes: 18 additions & 2 deletions lib/esbonio/esbonio/sphinx_agent/handlers/domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ def init_db(self, app: Sphinx):
project_names = [p[0] for p in projects]

for domain in app.env.domains.values():
index_domain(app, domain, project_names)
index_domain_directives(app, domain)
index_domain_roles(app, domain, project_names)

def commit(self, app, exc):
"""Commit changes to the database.
Expand Down Expand Up @@ -131,7 +132,22 @@ def object_defined(
self._info[key] = (description, location)


def index_domain(app: Sphinx, domain: Domain, projects: list[str] | None):
def index_domain_directives(app: Sphinx, domain: Domain):
"""Index the directives in the given domain.
Parameters
----------
app
The application instance
domain
The domain to index
"""
for name, directive in domain.directives.items():
app.esbonio.add_directive(f"{domain.name}:{name}", directive, [])


def index_domain_roles(app: Sphinx, domain: Domain, projects: list[str] | None):
"""Index the roles in the given domain.
Parameters
Expand Down

0 comments on commit bc535de

Please sign in to comment.