3 min read
pyssg.contrib.obsidian
Contrib: Obsidian vault integration.
Turns an Obsidian vault into a PySSG site with one call. It is a thin composition layer -- it owns no new build algorithm beyond attachment handling; everything else is the existing built-in pipeline wired with vault-friendly defaults:
- vault noise (
.obsidian/,.trash/,.git/) is excluded from the content walk via :func:pyssg.plugins.directory_loader'sexcludefilter; the Obsidian adapter discovers folder-specific excludes (Templates, daily notes) from the vault's own settings and passes them in too; - selective publishing is delegated to the :mod:
pyssg.contrib.publish_gateplugin (denylist by default for a whole-vault wiki: a note is published unless it setspublish: false; switch to an allowlist withpublish_required); - Hugo-style
_index.mdsection pages are routed to their directory root; wikilinks/embeds /#tagsare handled by the built-inwikilink/transclude/taxonomyplugins.
The one genuinely Obsidian-specific algorithm lives here: attachment embeds.
Obsidian writes image/file embeds as and stores the binary
anywhere in the vault. :class:ObsidianAttachmentsPlugin resolves such embeds by
filename, rewrites them to <img>/<a> pointing at a stable URL, and copies
the referenced binaries into the output -- the built-in pipeline only copies a
theme's assets, not vault attachments.
Everything is pure: the attachment index and the copy are deterministic functions of the on-disk vault, so builds stay byte-identical and incremental rebuilds equal full rebuilds.
rewrite_attachment_embeds(build: Build, html: str, exclude: tuple[str, ...]) -> str
finalize_content tap: resolve attachment embeds.
Only embeds whose target carries a non-.md extension are handled here;
note embeds () are left untouched for the transclude plugin. An
attachment that cannot be resolved renders as a broken-embed marker rather
than vanishing.
class ObsidianAttachmentsPlugin
Resolves embeds and copies vault attachments to output.
ObsidianAttachmentsPlugin.__init__(self, *, exclude: Sequence[str] | None = None) -> None
ObsidianAttachmentsPlugin.apply(self, builder: Builder) -> None
obsidian_attachments(*, exclude: Sequence[str] | None = None) -> ObsidianAttachmentsPlugin
Factory for the attachment plugin (used standalone or by :func:obsidian_plugins).
section_index_url(url: str, source_path: str | None) -> str
Map a Hugo-style _index.md page onto its directory root URL.
content/guide/_index.md is routed to /guide/ instead of
/guide/_index/ (and a top-level _index.md to /), so a vault using
the _index section-page convention gets real section landing pages without
an explicit permalink. Any other page is returned unchanged. Pure: the
result depends only on the URL and source path.
class ObsidianSectionIndexPlugin
Routes _index.md files to their directory root (Hugo section pages).
ObsidianSectionIndexPlugin.apply(self, builder: Builder) -> None
obsidian_section_index() -> ObsidianSectionIndexPlugin
Factory for the _index.md section-page router.
obsidian_plugins(*, include: Sequence[str] | None = None, exclude: Sequence[str] | None = None, publish_required: bool = False, publish_key: str = 'publish', section_index: bool = True, highlight_style: str = 'default', rss_title: str | None = None) -> list[Plugin]
Return the full Obsidian-flavored plugin pipeline, in apply order.
The default vault excludes (:data:DEFAULT_VAULT_EXCLUDE) are always applied;
exclude adds to them and include (if given) restricts which files load.
publish_required selects the publishing mode: the default False is a
denylist (every note is published unless it sets publish: false), suited to
a whole-vault wiki; pass True for an allowlist (publish only publish:
true notes). section_index (default on) routes Hugo-style _index.md
files to their directory root. Drop the result straight into a
:class:~pyssg.Config (Config(plugins=obsidian_plugins())) or splice extra
plugins after it.