Commits
Reachability audit of the opam-only driver turned up ~400 lines with no
live callers, mostly debug printers and machinery for the multi-package
and dune modes that earlier commits removed:
- opam.ml: deps, check, pkg_to_dir_map, dune_overrides, classify_libs,
find_odoc_config, the pp_* debug helpers and the package printer, plus
the installed_files / package_of_fpath / fpaths_of_package types.
all_opam_packages, pkg_contents, classify_docs and prefix stay (still
reached via voodoo.ml).
- packages.ml: the entire pretty-printing tree (pp, pp_intf, pp_impl,
pp_src_info, pp_modulety, pp_libty, pp_mld, pp_md, pp_asset, Module.pp,
Lib.pp) — no live root.
- odoc_unit.ml: the unit printer (pp / pp_kind / pp_intf_extra /
pp_impl_extra / pp_index) and the now-orphaned Pkg_args.pp.
- global_config.ml: load (parse/empty remain).
- landing_pages.mli: narrow the interface, make_index is only used
internally.
Build and testsuite unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The marshalled per-package partials are no longer produced or consumed:
the last consumers went in 4a4c7b4 (digest-based dep recovery) and
095bc33 (fix_virtual now probes the findlib cone). All that remained was
clean_previous deleting stale __odoc_partial.*.m files left by older
tool versions. Those files are inert now — never read, and not swept
into any index (they aren't .odoc/.odocl units) — so remove the cleanup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
odd-driver only ever runs the opam mode, but the driver still carried
the other entry points of odoc's driver it was vendored from. Since
the two have now diverged architecturally — units and pages installed
under lib/, scope from findlib, nothing persisted between runs — treat
the fork as a fork and delete what odd cannot reach:
- the voodoo-prep mode (prep/universes discovery, the flat/blessed
layouts, cmtidir threading);
- the monorepo mode (monorepo_style.ml, make_custom landing pages,
id_override, the sexp-derived config it parsed — and with it the
ppx_sexp_conv dependency);
- the monolithic mode (of_libs/of_packages, Ocamlfind.Db, db.ml,
status.ml, package_list, indices_style, fix_missing_deps and
lib_name_by_hash, already uncalled);
- the remap machinery and the to_output/selected flags, constant in
opam mode;
- the unused halves of Common_args and Util.
Also gitignore the driver's _index/_mld scratch dirs.
Behaviour is unchanged: warning counts and sherlodoc dbs are identical
across the 16-package comparison set, and the compiler sidebar remains
byte-identical to the pre-colocation driver's.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The [ocaml] package's pages root is the compiler's stdlib directory
(lib/ocaml), so sweeping index roots recursively pulled the compiler's
units into its search db, duplicating every stdlib entry in odd
search. Sweep kinds separately, as clean_previous does: pages and
assets recursively under the pages root (they mirror their ids),
module and implementation units from the library directories' top
level only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
odoc compile-index builds one sidebar hierarchy per --root, so a
package whose pages root and library directories are separate — the
compiler's pages live in lib/ocaml-compiler, its units in lib/ocaml —
got a split sidebar: page entries in one tree, module groups in
another. Files passed via --file-list all land in a single group, so
enumerate the roots' odocls ourselves and pass the list; the sidebar
is again byte-identical to the pre-colocation driver's.
Found by diffing the rendered HTML of 16 packages against main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A package's documentation pages (its mlds, generated landing pages,
assets) now live under lib/<pkg>, mirroring their ids — for most
packages the same directory as its libraries' units, and the id
mirroring is what makes page path references resolve ({!/pkg/dir/page}
is looked up as <-P root>/dir/page-....odoc). The odoc tree reduces to
rendered output: html, indexes, search dbs, occurrences.
The -P/-L scope now comes entirely from findlib plus the naming
convention that an opam package and its findlib libraries share a
toplevel name: a library's owner is its name's first segment, a
package's libraries are the findlib names under its toplevel
(Ocamlfind.sub_libraries), and a dependency's -P root is
lib/<pkg>, gated on its page-index.odoc existing. Renamed packages
(opam's ocamlfind installs findlib's findlib) merely lose page
references into them; module resolution never depended on the
correspondence. With that, nothing needs persisting between driver
runs at all: the partials, their Marshal format and version
discipline, and Voodoo.extra_paths are gone. odd-driver's --odocl-dir
flag goes too (odocls are co-located by construction).
clean_previous distinguishes artifact kinds: module units are swept
from library directories' top level only, pages and assets recursively
under the pages root (another package's units can nest beneath it —
compiler-libs under lib/ocaml), and anything under the package's
output tree, including legacy partials. Index roots are normalised to
directory paths before pruning nested roots — the trailing-slash
mismatch between lib.dir and lib_root/<pkg> otherwise pruned both
copies of the same root, yielding empty search indexes.
odd's reference scan walks installed packages' lib/<pkg> trees for
pages, page roots are documented packages' lib/<pkg> dirs, and
ambiguity suggestions qualify pages by their position under their
package root. The pre-remove hook also prunes the id-mirror
directories it empties.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A virtual library's implementation ships its modules as .cmt only; the
.mli and its documentation live with the virtual library, whose
directory is on the implementation's dependency cone (dune's
[implements] emits the META requires). So for each module documented
from a .cmt, probe the cone's directories for a same-name .cmti with
the same interface digest (odoc compile-deps reports it) and use that
as the input. The probe needs only the installed cmti file — not a
documented unit and not another package's partial — so it covers, in
one rule: implementations shipped with their virtual library
(digestif, checkseum — for which the old machinery found nothing,
because dune declares such virtual libraries with an empty archive
string and their cmtis never became units), implementations in other
opam packages, and the voodoo-era input_copy stash.
This replaces both remap_virtual (within-package, hash-map based) and
Odoc_unit.fix_virtual (cross-package, partial-fed), and with the
latter goes find_partials — compile scheduling only ever needed this
run's own units, since dependencies' units are found through -I — and
the input_copy field and stashing. Removing the field changes the
marshalled shape of Odoc_unit.t: partial format bumped to v3.
Partials are now written for a single reader: extra_paths_of_partials.
Verified: digestif and checkseum implementations now document from
the virtual cmti under their own ids; astring, fpath, tyxml, logs,
mirage-clock-unix unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Anchoring on the input file's directory collides for virtual
libraries: Packages.remap_virtual swaps an implementation's interface
for the virtual library's cmti before units are created, so the
virtual library and every implementation would all write
<virtualdir>/<name>.odoc, racing with different parent ids. The
output belongs with the unit's own library regardless of where its
input comes from; for ordinary modules the two anchors are the same
directory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix_missing_deps existed to patch library deps the META files were
assumed to omit, mapping module interface digests to providing
libraries via the partials of already-compiled dependencies. Measured
against a 25-package sample (base, core, async, merlin-lib, ppxlib,
eio, tyxml, ...), all 79 of its recoveries were already inside the
library's transitive findlib cone — the check was against the direct
lib_deps set, so transitively-declared dependencies looked missing.
With -I now the findlib cone, the recovery contributed nothing (per-
package warning counts are identical without it) while leaking its
additions into -L, silently widening the author-facing reference
scope beyond the documented direct set.
This removes the second of the three remaining partial consumers
(lib_name_by_hash_of_partials); fix_virtual and the -P attribution in
extra_paths_of_partials are what's left.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Split the search-path roles. -I is mechanical: a unit's library gets
the directories of its transitive findlib dependency cone — the same
path the compiler used to build it — so with units installed next to
their sources, every import's .odoc is present next to the .cmi the
compiler read, and odoc resolves each one among same-named candidates
by interface digest. This replaces the hash-precise include set
computed from partials, covers source links into functor bodies
mechanically (uutf is on tyxml.functor's cone), and, being
per-library, never puts a virtual library's sibling implementations
on each other's paths.
-L/-P revert to the documented author-facing scope: the package's own
libraries, their directly-declared META dependencies and
odoc-config.sexp additions — not the transitive closure, which the
cone made redundant and which systematically reunited virtual
libraries with their implementations on downstream paths (an
implementation's requires names its virtual library; with identical
interface digests the resolver's pick between them is arbitrary).
Library directories fall back to findlib when not known from the
packages in memory or the partials.
Verified on ocaml-compiler, astring, fmt, cmdliner, fpath, logs,
tyxml, ppxlib, react: warnings equal or fewer everywhere (fmt.cli's
Cmdliner references now resolve), functor source links intact.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completing "/fp" returned nothing: splitting the stem "/" on '/'
yields [""; ""], which the "//..." out-of-scope guard matched first,
making the root-names branch unreachable. Match the exact two-segment
case before the guard.
A package and a library can share a name — every package with a
top-level library does — with pages under the odoc tree and module
units in the library's physical directory. Completing under such a
name now lists the entries of every root it matches, so
"/fpath/Fp" -> "/fpath/Fpath" works again with co-located units.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Partials are raw Marshal output: decoding one written against a
different layout of Odoc_unit.t can segfault the driver, which is
exactly what happened reading partials from a build predating the
per-module deps field. Put the format version in the filename
(__odoc_partial.v2.m) so files from other versions are ignored — their
packages just count as not yet compiled — and treat undecodable files
as absent. Bump the version whenever the marshalled shape, or the
meaning of the recorded paths, changes.
clean_previous now also sweeps .odoc/.odocl files from the package's
odoc tree, not just its library directories: units left there by
pre-colocation builds otherwise get indexed alongside their co-located
replacements, and the duplicate entries break sherlodoc's db build
(suffix_tree assertion).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reference scan used to walk only the odoc/ tree; module units now
live next to their cmt/cmtis, so index the switch's library directories
too. findlib is the map from library name to directory — exactly where
the driver installs a library's units — so the -L named roots come from
it and /lib/Module path references keep resolving. Ambiguity
suggestions qualify module units by library name.
opam doesn't track the installed .odoc/.odocl files, so a new
pre-remove hook deletes them while the package's lib directory still
exists, letting opam prune the emptied directories as usual.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Module and implementation units now live in the library's physical
directory, alongside the cmt/cmti they were compiled from, instead of
under the switch's odoc tree. odoc compile pins its output location to
--output-dir/--parent-id, so the driver lets it write there and then
moves the file; the unit's identity (and hence every URL) is stored
inside the file and is unaffected. odoc link takes -o, so odocls are
written in place directly. Placement is anchored to the unit's own
library at unit-creation time, so fix_virtual swapping a virtual
library's input for another package's cmti doesn't relocate the output.
Pages, assets and landing pages have no source file and stay under the
odoc tree; -P/page roots are unchanged. -L roots and link includes now
point at the physical library directories. Sub-libraries of old-style
packages can share a directory, so those roots don't always form the
antichain odoc link checks for: link with --custom-layout.
The lib/pkg marker files are gone. Everything they recorded is
reconstructed from the partials of earlier runs (extra_paths_of_partials):
each partial unit carries its library name, its package and its odoc
file, giving the physical -L/-I directories, the -P pages directories
and the package->libraries grouping exactly, including for switches
last built by an older odd (their partials point at the old tree
locations, where those units still are).
Since orphaned units next to the sources would be swept into the
package's indexes, clean_previous removes co-located .odoc/.odocl files
and the package's partial before recompiling it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The -L set was only the directly-declared library dependencies of the
package's libraries, relying on digest recovery for anything the META
files omit. Source-link resolution needs more: rendering a source link
into a functor's body requires the functor library's own dependencies
(e.g. uutf for tyxml.functor) on the path too, so close the set
transitively over every library we know about.
Also pick up archives declared only under the ppx_driver predicate
(ppxlib.traverse, ppxlib.metaquot, ...) when reading META files, instead
of dropping them here and re-discovering them via the no-META fallback
under a mangled name (ppxlib_traverse).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compute the -L/-P/-I arguments to `odoc link` once per package (shared by all
its libraries and pages) rather than per library from a transitively-closed
dependency set:
- -L is the directly-declared META dependencies of the package's libraries
(own libraries included) plus anything from odoc-config.sexp; no transitive
closure is taken here.
- -P is every package providing one of those libraries, plus the package
itself and any odoc-config.sexp packages.
Dependencies used but not declared in META (e.g. tyxml.functor, reached only via
tyxml's META) are recovered by digest, not closure. `fix_missing_deps` is split
into `lib_name_by_hash` + `fix_missing_deps_with`, which the voodoo driver now
runs, sourcing the digest->library map from the `lib_name` recorded in
dependencies' partials; `intf_extra`/`impl_extra` gain a `lib_name` field, and
`ocamlfind`/`of_voodoo` now yield direct (un-closed) META deps.
Mirrors the same change in odoc's src/driver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive the include set passed to `odoc compile`/`compile-impl` from the
unit's actual per-module dependencies rather than the compiled library's
whole dependency set. Each dependency carries the interface hash of the
module it refers to; look that hash up among the units being compiled
plus the partials of already-compiled dependencies, and take the
directory of the providing unit.
This is more precise (far fewer `-I` flags) and doesn't rely on the META
files declaring every transitively-needed library — which is what left
`Html_types` (reached only through `tyxml`'s META edge to
`tyxml.functor`) unresolved in eliom's docs. Applies to both interfaces
and implementations; `impl_extra` now carries the implementation's
dependency list.
Mirrors the same change in odoc's src/driver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`odd show` is now `odd doc`. Renamed the subcommand and its implementation
module (Show -> Doc, lib/show.ml -> lib/doc.ml, Show.show -> Doc.doc), the zsh
completion's dispatch and command list, and the docs (doc/DESIGN_SHOW.md ->
doc/DESIGN_DOC.md). Also fixed some stale Show.scan/Show.owns references in the
design docs that should have been Refs.* since the Refs extraction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the bundled driver binary from odoc_driver_opam to odd-driver so
`opam install odd` doesn't clash with the odoc-driver package, which owns a
binary of that name. `odd sync` defaults to `odd-driver` accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make odd self-contained: import odoc_driver_lib and the opam-mode entry point
from odoc-drivermod (src/driver) into driver/, built as a private library plus
the `odoc_driver_opam` binary installed alongside `odd`. `odd sync` invokes it
as before. Warnings are silenced for the vendored code so odd's strict dev
profile builds it as-is. Adds its dependency cone (eio, progress, yojson,
ocamlfind, logs, sexplib, ppx_sexp_conv, odoc-md) to the package.
Temporary, pending upstreaming of the driver changes into odoc-driver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the package, binary, and library from odoc-switchdocs/switchdocs to
odd: opam package `odd`, command `odd <subcommand>`, library module `Odd`.
Knock-on renames: the stale marker is now `.odd-stale`, state lives in
var/cache/odd, the SWITCHDOCS_DRIVER env var becomes ODD_DRIVER, the zsh
completion is _odd, and the opam hooks and docs refer to `odd`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a reference's root name matches more than one .odocl file across the
switch (the classic case is a bare `index`, a page present in every package),
list the package-qualified alternatives and exit non-zero rather than silently
rendering whichever the resolver picked first. The alternatives are derived
from each candidate file's path under odoc/ (odoc/astring/page-index.odocl ->
/astring/index) with the original reference's tail re-attached.
Refs gains root_candidates. Unaffected: unique references (one candidate),
already-qualified path references, and the common case where a name resolves
to a single file (e.g. List.map -> Stdlib, one stdlib.odocl).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every rendering now starts with a line like
`**Stdlib.List.map** — *stdlib (ocaml-compiler)*`: the resolved full name and
where it comes from. The package and library are read from the item's URL path
(<pkg>/<lib>/<Module>/...): package is the first component, library the page
just before the first module component (a doc page has none). The library is
collapsed when it equals the package, matching `search`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The driver writes HTML at <pkg>/<lib>/<Module>/..., so the library is the
second path component of an entry's url. Add it to the result and print it
before the package, e.g. "stdlib (ocaml-compiler)". Collapsed when it adds
nothing (a library named after its package, or a doc/page entry with no
library — gated on the entry kind).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cleaner and layout-robust than hardcoding $OPAM_SWITCH_PREFIX/share/...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The switch's share/zsh/site-functions is not on zsh's $fpath by default, so
that layout implied an auto-discovery that doesn't happen. Install to the
package's own share/odoc-switchdocs/zsh/_switchdocs instead; the user adds it
to $fpath either way. README and the file header updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Path completion handled only `/` (root names) and `/root/` (one level); a
deeper path like `/odoc/deprecated/` fell through to the empty case. Generalise
it: the first segment resolves to its root directory, any further segments are
literal sub-directories beneath it, and the final directory's entries (units,
pages, sub-dirs) are offered. So `/odoc/deprecated/` now completes the pages in
odoc/odoc/deprecated/. Current-package (`//…`) and relative (`./…`) forms still
return nothing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
completions/zsh/_switchdocs completes the subcommands and, for `show` and
`complete`, the reference argument by handing the current word to
`switchdocs complete` and feeding the full reference strings to compadd (empty
suffix, so dotted/slashed refs can be drilled into). Because it reuses the
command's own resolver, completion covers Stdlib-open bare names,
package-qualified /pkg/... paths, kind tags and section labels for free.
Installed to <prefix>/share/zsh/site-functions via a dune install stanza.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A doc comment's section headings ({1 …}, {1:foo …}) are referenceable as
[label-]/[section-] children of their enclosing module or page. Enumerate
them: for a module, from its signature's top-comment and standalone comment
items (alongside the existing members); for a page, from its content. Both
explicit and auto-generated labels are offered.
So `Astring.label-` -> Astring.label-port, ...; `/odoc/index.` ->
/odoc/index.overview, ...; and a bare `Module.` now lists sections too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Given a partial reference, list the full references it could become — the
reverse of `show`: resolve the parent context and enumerate its children
matching the final, incomplete component. Built on the shared Refs module.
Forms handled:
- dotted members, with kind tags: `List.m` -> List.map, ...;
`module-List.type-` -> module-List.type-t (Stdlib open, so bare names work)
- path roots: `/o` -> /odoc, /odoc.model, /ocaml-compiler, ...
- units under a root: `/odoc.model/` -> /odoc.model/Odoc_model, ...
- path + dotted: `/odoc.model/Odoc_model.Pa` -> .../Odoc_model.Paths
- bare top-level names: units + open-module members
Output preserves the verbatim stem + kind prefix and appends the matched
name. Input is split with a tolerant scanner (odoc's tokenizer rejects the
empty trailing identifier we need). Wrapped internal modules (names with
`__`) are filtered as hidden. Resolution failures yield no completions, not
errors. See DESIGN_COMPLETE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
show.ml mixed two concerns: resolving a reference against the switch (scan,
resolve, load the owning .odocl, locate the item) and rendering the result as
Markdown. Split the former into a new Refs module so a forthcoming `complete`
command can share it without duplicating the Stdlib-open / -L,-P-roots /
ikey-disambiguation logic.
Refs exposes: scan, resolve_to_id, a `located` view of what an identifier
points at (module/class/type/leaf, with the structure needed to enumerate
children), find, and owning_content. Show becomes a thin renderer on top.
Behaviour and the cram tests are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pass ~open_modules:["Stdlib"] to Resolver.create, mirroring the compiler's
(and the driver's link-time) default environment, so `switchdocs show List`
resolves to Stdlib.List and `switchdocs show print_endline` works. Modules
not documented in the switch are skipped by Env.open_units, so this is safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The post-install/post-remove hooks previously ran `switchdocs record`, so a
switch where switchdocs was absent (uninstalled, mid-upgrade, or a global
hook firing for a switch that never had it) silently lost the recording.
Replace the recorder with plain coreutils:
- post-install: `mkdir -p` + `touch <prefix>/odoc/<pkg>/.switchdocs-stale`
- post-remove: `rm -rf <prefix>/odoc/<pkg>`
- post-session: `switchdocs sync` (the only hook that needs the binary)
`sync` now collects its work list by scanning for `.switchdocs-stale`
markers (intersected with the installed set), rebuilds those in dependency
order — erasing each first (which clears the marker), re-marking on failure
so it retries — and regenerates the landing page *every session* (skipping
the write when unchanged) so packages deleted by the post-remove hook drop
off it without needing a removal marker.
Drop the `record` subcommand, the pending file, and the `removed` field of
the sync outcome. `Pending` becomes the stale-marker module (`read`/`mark`);
`rebuild` marks via `Pending.mark`. Cram test and docs updated; `record.t`
removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
opam installs each package's own documentation into <prefix>/doc/<pkg>, so
sharing that tree risks clobbering it. Move switchdocs' generated odoc/odocl/
HTML tree to the sibling <prefix>/odoc, matching odoc_driver_opam's new
default output directory.
Switch.doc_dir is renamed to Switch.odoc_dir (it now returns <prefix>/odoc),
with all callers (sync, search, index_page, show) and the cram test's fake
driver updated. Docs and READMEs adjusted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconstruct the driver's named roots for the whole switch — one page root
(-P) per package (doc/<pkg>) and one lib root (-L) per library
(doc/<pkg>/<lib>) — and pass them to Resolver.create. This makes
package-qualified path references resolve, e.g.:
switchdocs show /stdlib/Stdlib.List.map
switchdocs show /astring/Astring
switchdocs show /astring/index
current_lib/current_package are left None (show has no "current" unit, so
relative and current-package path references don't apply; absolute /pkg/...
ones do). The named-root dirs are a subset of the existing -I set, so
ordinary dotted-name resolution is unchanged.
Loading is generalised to handle both compilation units and mld pages: the
file owning an identifier is found by matching the identifier-key suffix
(owns), which also disambiguates the many identically-named page-index.odocl
files across packages. Doc pages render in full via document_of_page.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously `show` printed only an item's top doc comment. For a module that
threw away the body. Now, when the reference resolves to something that owns
its own page (module, module type, class, or the unit itself — detected by an
empty URL anchor), render the full page via document_of_compilation_unit plus
the markdown Generator, picking the page whose Url.Path matches the target.
Leaf items (val, type, exception, …) still render just their doc comment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Print the documentation of an item named by an odoc reference, e.g.
`switchdocs show Stdlib.List.map`, rendered as Markdown.
This is the reference-resolution half of `odoc link` run standalone over
the whole switch: scan `<prefix>/doc` for the include dirs and a linked-unit
index, parse and resolve the reference with Odoc_xref2.Ref_tools against the
switch's .odoc files, then load the owning unit's .odocl and walk its Lang
tree to the item's doc comment and render it via the odoc.markdown backend.
The owning unit among same-named candidates (e.g. two Stdlib.odocl) is
selected by matching the resolved identifier's key suffix, so the loaded
unit stays consistent with what the reference resolved into. Module targets
fall back to their signature's top-comment when the declaration itself is
undocumented.
See DESIGN_SHOW.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Search the switch's documentation from the command line. Loads every
per-package sherlodoc_db.marshal under $OPAM_SWITCH_PREFIX/doc and
queries them together — sherlodoc's API searches a list of databases
and unions the results — so a single query covers the whole switch,
with each result labelled by its owning package. --package restricts
to named packages; -n caps the result count.
A database that fails to load (e.g. written by an incompatible
sherlodoc version) is skipped with a warning rather than aborting the
search.
Adds a dependency on sherlodoc (query + db_store libraries).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A cmdliner CLI with five subcommands: record (hook recorder, one atomic
append), sync (session worker: erase removed, rebuild stale in dependency
order via odoc_driver_opam, regenerate landing page), rebuild (manual
escape hatch), order (debugging aid) and setup (configure the opam wrapper
fields via opam option).
Hook-facing commands always exit 0 so documentation failures never fail an
opam invocation; details go to var/cache/switchdocs/log and failed
packages stay pending for retry by the next session.
Dependency order is computed at sync time from
.opam-switch/packages/*/opam via opam-format, excluding {post} deps and
including depopts. Covered by cram tests using a fake switch layout and a
stub driver; the opam option append syntax used by setup --apply was
verified against a scratch opam root.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The opam-mode driver reads package file lists from opam's .changes
files in place, removing the need for a voodoo-prep-style input tree,
and uses the flat <package>/ output layout with directories defaulting
to the switch doc dir.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reachability audit of the opam-only driver turned up ~400 lines with no
live callers, mostly debug printers and machinery for the multi-package
and dune modes that earlier commits removed:
- opam.ml: deps, check, pkg_to_dir_map, dune_overrides, classify_libs,
find_odoc_config, the pp_* debug helpers and the package printer, plus
the installed_files / package_of_fpath / fpaths_of_package types.
all_opam_packages, pkg_contents, classify_docs and prefix stay (still
reached via voodoo.ml).
- packages.ml: the entire pretty-printing tree (pp, pp_intf, pp_impl,
pp_src_info, pp_modulety, pp_libty, pp_mld, pp_md, pp_asset, Module.pp,
Lib.pp) — no live root.
- odoc_unit.ml: the unit printer (pp / pp_kind / pp_intf_extra /
pp_impl_extra / pp_index) and the now-orphaned Pkg_args.pp.
- global_config.ml: load (parse/empty remain).
- landing_pages.mli: narrow the interface, make_index is only used
internally.
Build and testsuite unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The marshalled per-package partials are no longer produced or consumed:
the last consumers went in 4a4c7b4 (digest-based dep recovery) and
095bc33 (fix_virtual now probes the findlib cone). All that remained was
clean_previous deleting stale __odoc_partial.*.m files left by older
tool versions. Those files are inert now — never read, and not swept
into any index (they aren't .odoc/.odocl units) — so remove the cleanup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
odd-driver only ever runs the opam mode, but the driver still carried
the other entry points of odoc's driver it was vendored from. Since
the two have now diverged architecturally — units and pages installed
under lib/, scope from findlib, nothing persisted between runs — treat
the fork as a fork and delete what odd cannot reach:
- the voodoo-prep mode (prep/universes discovery, the flat/blessed
layouts, cmtidir threading);
- the monorepo mode (monorepo_style.ml, make_custom landing pages,
id_override, the sexp-derived config it parsed — and with it the
ppx_sexp_conv dependency);
- the monolithic mode (of_libs/of_packages, Ocamlfind.Db, db.ml,
status.ml, package_list, indices_style, fix_missing_deps and
lib_name_by_hash, already uncalled);
- the remap machinery and the to_output/selected flags, constant in
opam mode;
- the unused halves of Common_args and Util.
Also gitignore the driver's _index/_mld scratch dirs.
Behaviour is unchanged: warning counts and sherlodoc dbs are identical
across the 16-package comparison set, and the compiler sidebar remains
byte-identical to the pre-colocation driver's.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The [ocaml] package's pages root is the compiler's stdlib directory
(lib/ocaml), so sweeping index roots recursively pulled the compiler's
units into its search db, duplicating every stdlib entry in odd
search. Sweep kinds separately, as clean_previous does: pages and
assets recursively under the pages root (they mirror their ids),
module and implementation units from the library directories' top
level only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
odoc compile-index builds one sidebar hierarchy per --root, so a
package whose pages root and library directories are separate — the
compiler's pages live in lib/ocaml-compiler, its units in lib/ocaml —
got a split sidebar: page entries in one tree, module groups in
another. Files passed via --file-list all land in a single group, so
enumerate the roots' odocls ourselves and pass the list; the sidebar
is again byte-identical to the pre-colocation driver's.
Found by diffing the rendered HTML of 16 packages against main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A package's documentation pages (its mlds, generated landing pages,
assets) now live under lib/<pkg>, mirroring their ids — for most
packages the same directory as its libraries' units, and the id
mirroring is what makes page path references resolve ({!/pkg/dir/page}
is looked up as <-P root>/dir/page-....odoc). The odoc tree reduces to
rendered output: html, indexes, search dbs, occurrences.
The -P/-L scope now comes entirely from findlib plus the naming
convention that an opam package and its findlib libraries share a
toplevel name: a library's owner is its name's first segment, a
package's libraries are the findlib names under its toplevel
(Ocamlfind.sub_libraries), and a dependency's -P root is
lib/<pkg>, gated on its page-index.odoc existing. Renamed packages
(opam's ocamlfind installs findlib's findlib) merely lose page
references into them; module resolution never depended on the
correspondence. With that, nothing needs persisting between driver
runs at all: the partials, their Marshal format and version
discipline, and Voodoo.extra_paths are gone. odd-driver's --odocl-dir
flag goes too (odocls are co-located by construction).
clean_previous distinguishes artifact kinds: module units are swept
from library directories' top level only, pages and assets recursively
under the pages root (another package's units can nest beneath it —
compiler-libs under lib/ocaml), and anything under the package's
output tree, including legacy partials. Index roots are normalised to
directory paths before pruning nested roots — the trailing-slash
mismatch between lib.dir and lib_root/<pkg> otherwise pruned both
copies of the same root, yielding empty search indexes.
odd's reference scan walks installed packages' lib/<pkg> trees for
pages, page roots are documented packages' lib/<pkg> dirs, and
ambiguity suggestions qualify pages by their position under their
package root. The pre-remove hook also prunes the id-mirror
directories it empties.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A virtual library's implementation ships its modules as .cmt only; the
.mli and its documentation live with the virtual library, whose
directory is on the implementation's dependency cone (dune's
[implements] emits the META requires). So for each module documented
from a .cmt, probe the cone's directories for a same-name .cmti with
the same interface digest (odoc compile-deps reports it) and use that
as the input. The probe needs only the installed cmti file — not a
documented unit and not another package's partial — so it covers, in
one rule: implementations shipped with their virtual library
(digestif, checkseum — for which the old machinery found nothing,
because dune declares such virtual libraries with an empty archive
string and their cmtis never became units), implementations in other
opam packages, and the voodoo-era input_copy stash.
This replaces both remap_virtual (within-package, hash-map based) and
Odoc_unit.fix_virtual (cross-package, partial-fed), and with the
latter goes find_partials — compile scheduling only ever needed this
run's own units, since dependencies' units are found through -I — and
the input_copy field and stashing. Removing the field changes the
marshalled shape of Odoc_unit.t: partial format bumped to v3.
Partials are now written for a single reader: extra_paths_of_partials.
Verified: digestif and checkseum implementations now document from
the virtual cmti under their own ids; astring, fpath, tyxml, logs,
mirage-clock-unix unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Anchoring on the input file's directory collides for virtual
libraries: Packages.remap_virtual swaps an implementation's interface
for the virtual library's cmti before units are created, so the
virtual library and every implementation would all write
<virtualdir>/<name>.odoc, racing with different parent ids. The
output belongs with the unit's own library regardless of where its
input comes from; for ordinary modules the two anchors are the same
directory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix_missing_deps existed to patch library deps the META files were
assumed to omit, mapping module interface digests to providing
libraries via the partials of already-compiled dependencies. Measured
against a 25-package sample (base, core, async, merlin-lib, ppxlib,
eio, tyxml, ...), all 79 of its recoveries were already inside the
library's transitive findlib cone — the check was against the direct
lib_deps set, so transitively-declared dependencies looked missing.
With -I now the findlib cone, the recovery contributed nothing (per-
package warning counts are identical without it) while leaking its
additions into -L, silently widening the author-facing reference
scope beyond the documented direct set.
This removes the second of the three remaining partial consumers
(lib_name_by_hash_of_partials); fix_virtual and the -P attribution in
extra_paths_of_partials are what's left.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Split the search-path roles. -I is mechanical: a unit's library gets
the directories of its transitive findlib dependency cone — the same
path the compiler used to build it — so with units installed next to
their sources, every import's .odoc is present next to the .cmi the
compiler read, and odoc resolves each one among same-named candidates
by interface digest. This replaces the hash-precise include set
computed from partials, covers source links into functor bodies
mechanically (uutf is on tyxml.functor's cone), and, being
per-library, never puts a virtual library's sibling implementations
on each other's paths.
-L/-P revert to the documented author-facing scope: the package's own
libraries, their directly-declared META dependencies and
odoc-config.sexp additions — not the transitive closure, which the
cone made redundant and which systematically reunited virtual
libraries with their implementations on downstream paths (an
implementation's requires names its virtual library; with identical
interface digests the resolver's pick between them is arbitrary).
Library directories fall back to findlib when not known from the
packages in memory or the partials.
Verified on ocaml-compiler, astring, fmt, cmdliner, fpath, logs,
tyxml, ppxlib, react: warnings equal or fewer everywhere (fmt.cli's
Cmdliner references now resolve), functor source links intact.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completing "/fp" returned nothing: splitting the stem "/" on '/'
yields [""; ""], which the "//..." out-of-scope guard matched first,
making the root-names branch unreachable. Match the exact two-segment
case before the guard.
A package and a library can share a name — every package with a
top-level library does — with pages under the odoc tree and module
units in the library's physical directory. Completing under such a
name now lists the entries of every root it matches, so
"/fpath/Fp" -> "/fpath/Fpath" works again with co-located units.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Partials are raw Marshal output: decoding one written against a
different layout of Odoc_unit.t can segfault the driver, which is
exactly what happened reading partials from a build predating the
per-module deps field. Put the format version in the filename
(__odoc_partial.v2.m) so files from other versions are ignored — their
packages just count as not yet compiled — and treat undecodable files
as absent. Bump the version whenever the marshalled shape, or the
meaning of the recorded paths, changes.
clean_previous now also sweeps .odoc/.odocl files from the package's
odoc tree, not just its library directories: units left there by
pre-colocation builds otherwise get indexed alongside their co-located
replacements, and the duplicate entries break sherlodoc's db build
(suffix_tree assertion).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reference scan used to walk only the odoc/ tree; module units now
live next to their cmt/cmtis, so index the switch's library directories
too. findlib is the map from library name to directory — exactly where
the driver installs a library's units — so the -L named roots come from
it and /lib/Module path references keep resolving. Ambiguity
suggestions qualify module units by library name.
opam doesn't track the installed .odoc/.odocl files, so a new
pre-remove hook deletes them while the package's lib directory still
exists, letting opam prune the emptied directories as usual.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Module and implementation units now live in the library's physical
directory, alongside the cmt/cmti they were compiled from, instead of
under the switch's odoc tree. odoc compile pins its output location to
--output-dir/--parent-id, so the driver lets it write there and then
moves the file; the unit's identity (and hence every URL) is stored
inside the file and is unaffected. odoc link takes -o, so odocls are
written in place directly. Placement is anchored to the unit's own
library at unit-creation time, so fix_virtual swapping a virtual
library's input for another package's cmti doesn't relocate the output.
Pages, assets and landing pages have no source file and stay under the
odoc tree; -P/page roots are unchanged. -L roots and link includes now
point at the physical library directories. Sub-libraries of old-style
packages can share a directory, so those roots don't always form the
antichain odoc link checks for: link with --custom-layout.
The lib/pkg marker files are gone. Everything they recorded is
reconstructed from the partials of earlier runs (extra_paths_of_partials):
each partial unit carries its library name, its package and its odoc
file, giving the physical -L/-I directories, the -P pages directories
and the package->libraries grouping exactly, including for switches
last built by an older odd (their partials point at the old tree
locations, where those units still are).
Since orphaned units next to the sources would be swept into the
package's indexes, clean_previous removes co-located .odoc/.odocl files
and the package's partial before recompiling it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The -L set was only the directly-declared library dependencies of the
package's libraries, relying on digest recovery for anything the META
files omit. Source-link resolution needs more: rendering a source link
into a functor's body requires the functor library's own dependencies
(e.g. uutf for tyxml.functor) on the path too, so close the set
transitively over every library we know about.
Also pick up archives declared only under the ppx_driver predicate
(ppxlib.traverse, ppxlib.metaquot, ...) when reading META files, instead
of dropping them here and re-discovering them via the no-META fallback
under a mangled name (ppxlib_traverse).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compute the -L/-P/-I arguments to `odoc link` once per package (shared by all
its libraries and pages) rather than per library from a transitively-closed
dependency set:
- -L is the directly-declared META dependencies of the package's libraries
(own libraries included) plus anything from odoc-config.sexp; no transitive
closure is taken here.
- -P is every package providing one of those libraries, plus the package
itself and any odoc-config.sexp packages.
Dependencies used but not declared in META (e.g. tyxml.functor, reached only via
tyxml's META) are recovered by digest, not closure. `fix_missing_deps` is split
into `lib_name_by_hash` + `fix_missing_deps_with`, which the voodoo driver now
runs, sourcing the digest->library map from the `lib_name` recorded in
dependencies' partials; `intf_extra`/`impl_extra` gain a `lib_name` field, and
`ocamlfind`/`of_voodoo` now yield direct (un-closed) META deps.
Mirrors the same change in odoc's src/driver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive the include set passed to `odoc compile`/`compile-impl` from the
unit's actual per-module dependencies rather than the compiled library's
whole dependency set. Each dependency carries the interface hash of the
module it refers to; look that hash up among the units being compiled
plus the partials of already-compiled dependencies, and take the
directory of the providing unit.
This is more precise (far fewer `-I` flags) and doesn't rely on the META
files declaring every transitively-needed library — which is what left
`Html_types` (reached only through `tyxml`'s META edge to
`tyxml.functor`) unresolved in eliom's docs. Applies to both interfaces
and implementations; `impl_extra` now carries the implementation's
dependency list.
Mirrors the same change in odoc's src/driver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`odd show` is now `odd doc`. Renamed the subcommand and its implementation
module (Show -> Doc, lib/show.ml -> lib/doc.ml, Show.show -> Doc.doc), the zsh
completion's dispatch and command list, and the docs (doc/DESIGN_SHOW.md ->
doc/DESIGN_DOC.md). Also fixed some stale Show.scan/Show.owns references in the
design docs that should have been Refs.* since the Refs extraction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make odd self-contained: import odoc_driver_lib and the opam-mode entry point
from odoc-drivermod (src/driver) into driver/, built as a private library plus
the `odoc_driver_opam` binary installed alongside `odd`. `odd sync` invokes it
as before. Warnings are silenced for the vendored code so odd's strict dev
profile builds it as-is. Adds its dependency cone (eio, progress, yojson,
ocamlfind, logs, sexplib, ppx_sexp_conv, odoc-md) to the package.
Temporary, pending upstreaming of the driver changes into odoc-driver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the package, binary, and library from odoc-switchdocs/switchdocs to
odd: opam package `odd`, command `odd <subcommand>`, library module `Odd`.
Knock-on renames: the stale marker is now `.odd-stale`, state lives in
var/cache/odd, the SWITCHDOCS_DRIVER env var becomes ODD_DRIVER, the zsh
completion is _odd, and the opam hooks and docs refer to `odd`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a reference's root name matches more than one .odocl file across the
switch (the classic case is a bare `index`, a page present in every package),
list the package-qualified alternatives and exit non-zero rather than silently
rendering whichever the resolver picked first. The alternatives are derived
from each candidate file's path under odoc/ (odoc/astring/page-index.odocl ->
/astring/index) with the original reference's tail re-attached.
Refs gains root_candidates. Unaffected: unique references (one candidate),
already-qualified path references, and the common case where a name resolves
to a single file (e.g. List.map -> Stdlib, one stdlib.odocl).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every rendering now starts with a line like
`**Stdlib.List.map** — *stdlib (ocaml-compiler)*`: the resolved full name and
where it comes from. The package and library are read from the item's URL path
(<pkg>/<lib>/<Module>/...): package is the first component, library the page
just before the first module component (a doc page has none). The library is
collapsed when it equals the package, matching `search`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The driver writes HTML at <pkg>/<lib>/<Module>/..., so the library is the
second path component of an entry's url. Add it to the result and print it
before the package, e.g. "stdlib (ocaml-compiler)". Collapsed when it adds
nothing (a library named after its package, or a doc/page entry with no
library — gated on the entry kind).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The switch's share/zsh/site-functions is not on zsh's $fpath by default, so
that layout implied an auto-discovery that doesn't happen. Install to the
package's own share/odoc-switchdocs/zsh/_switchdocs instead; the user adds it
to $fpath either way. README and the file header updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Path completion handled only `/` (root names) and `/root/` (one level); a
deeper path like `/odoc/deprecated/` fell through to the empty case. Generalise
it: the first segment resolves to its root directory, any further segments are
literal sub-directories beneath it, and the final directory's entries (units,
pages, sub-dirs) are offered. So `/odoc/deprecated/` now completes the pages in
odoc/odoc/deprecated/. Current-package (`//…`) and relative (`./…`) forms still
return nothing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
completions/zsh/_switchdocs completes the subcommands and, for `show` and
`complete`, the reference argument by handing the current word to
`switchdocs complete` and feeding the full reference strings to compadd (empty
suffix, so dotted/slashed refs can be drilled into). Because it reuses the
command's own resolver, completion covers Stdlib-open bare names,
package-qualified /pkg/... paths, kind tags and section labels for free.
Installed to <prefix>/share/zsh/site-functions via a dune install stanza.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A doc comment's section headings ({1 …}, {1:foo …}) are referenceable as
[label-]/[section-] children of their enclosing module or page. Enumerate
them: for a module, from its signature's top-comment and standalone comment
items (alongside the existing members); for a page, from its content. Both
explicit and auto-generated labels are offered.
So `Astring.label-` -> Astring.label-port, ...; `/odoc/index.` ->
/odoc/index.overview, ...; and a bare `Module.` now lists sections too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Given a partial reference, list the full references it could become — the
reverse of `show`: resolve the parent context and enumerate its children
matching the final, incomplete component. Built on the shared Refs module.
Forms handled:
- dotted members, with kind tags: `List.m` -> List.map, ...;
`module-List.type-` -> module-List.type-t (Stdlib open, so bare names work)
- path roots: `/o` -> /odoc, /odoc.model, /ocaml-compiler, ...
- units under a root: `/odoc.model/` -> /odoc.model/Odoc_model, ...
- path + dotted: `/odoc.model/Odoc_model.Pa` -> .../Odoc_model.Paths
- bare top-level names: units + open-module members
Output preserves the verbatim stem + kind prefix and appends the matched
name. Input is split with a tolerant scanner (odoc's tokenizer rejects the
empty trailing identifier we need). Wrapped internal modules (names with
`__`) are filtered as hidden. Resolution failures yield no completions, not
errors. See DESIGN_COMPLETE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
show.ml mixed two concerns: resolving a reference against the switch (scan,
resolve, load the owning .odocl, locate the item) and rendering the result as
Markdown. Split the former into a new Refs module so a forthcoming `complete`
command can share it without duplicating the Stdlib-open / -L,-P-roots /
ikey-disambiguation logic.
Refs exposes: scan, resolve_to_id, a `located` view of what an identifier
points at (module/class/type/leaf, with the structure needed to enumerate
children), find, and owning_content. Show becomes a thin renderer on top.
Behaviour and the cram tests are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pass ~open_modules:["Stdlib"] to Resolver.create, mirroring the compiler's
(and the driver's link-time) default environment, so `switchdocs show List`
resolves to Stdlib.List and `switchdocs show print_endline` works. Modules
not documented in the switch are skipped by Env.open_units, so this is safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The post-install/post-remove hooks previously ran `switchdocs record`, so a
switch where switchdocs was absent (uninstalled, mid-upgrade, or a global
hook firing for a switch that never had it) silently lost the recording.
Replace the recorder with plain coreutils:
- post-install: `mkdir -p` + `touch <prefix>/odoc/<pkg>/.switchdocs-stale`
- post-remove: `rm -rf <prefix>/odoc/<pkg>`
- post-session: `switchdocs sync` (the only hook that needs the binary)
`sync` now collects its work list by scanning for `.switchdocs-stale`
markers (intersected with the installed set), rebuilds those in dependency
order — erasing each first (which clears the marker), re-marking on failure
so it retries — and regenerates the landing page *every session* (skipping
the write when unchanged) so packages deleted by the post-remove hook drop
off it without needing a removal marker.
Drop the `record` subcommand, the pending file, and the `removed` field of
the sync outcome. `Pending` becomes the stale-marker module (`read`/`mark`);
`rebuild` marks via `Pending.mark`. Cram test and docs updated; `record.t`
removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
opam installs each package's own documentation into <prefix>/doc/<pkg>, so
sharing that tree risks clobbering it. Move switchdocs' generated odoc/odocl/
HTML tree to the sibling <prefix>/odoc, matching odoc_driver_opam's new
default output directory.
Switch.doc_dir is renamed to Switch.odoc_dir (it now returns <prefix>/odoc),
with all callers (sync, search, index_page, show) and the cram test's fake
driver updated. Docs and READMEs adjusted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconstruct the driver's named roots for the whole switch — one page root
(-P) per package (doc/<pkg>) and one lib root (-L) per library
(doc/<pkg>/<lib>) — and pass them to Resolver.create. This makes
package-qualified path references resolve, e.g.:
switchdocs show /stdlib/Stdlib.List.map
switchdocs show /astring/Astring
switchdocs show /astring/index
current_lib/current_package are left None (show has no "current" unit, so
relative and current-package path references don't apply; absolute /pkg/...
ones do). The named-root dirs are a subset of the existing -I set, so
ordinary dotted-name resolution is unchanged.
Loading is generalised to handle both compilation units and mld pages: the
file owning an identifier is found by matching the identifier-key suffix
(owns), which also disambiguates the many identically-named page-index.odocl
files across packages. Doc pages render in full via document_of_page.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously `show` printed only an item's top doc comment. For a module that
threw away the body. Now, when the reference resolves to something that owns
its own page (module, module type, class, or the unit itself — detected by an
empty URL anchor), render the full page via document_of_compilation_unit plus
the markdown Generator, picking the page whose Url.Path matches the target.
Leaf items (val, type, exception, …) still render just their doc comment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Print the documentation of an item named by an odoc reference, e.g.
`switchdocs show Stdlib.List.map`, rendered as Markdown.
This is the reference-resolution half of `odoc link` run standalone over
the whole switch: scan `<prefix>/doc` for the include dirs and a linked-unit
index, parse and resolve the reference with Odoc_xref2.Ref_tools against the
switch's .odoc files, then load the owning unit's .odocl and walk its Lang
tree to the item's doc comment and render it via the odoc.markdown backend.
The owning unit among same-named candidates (e.g. two Stdlib.odocl) is
selected by matching the resolved identifier's key suffix, so the loaded
unit stays consistent with what the reference resolved into. Module targets
fall back to their signature's top-comment when the declaration itself is
undocumented.
See DESIGN_SHOW.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Search the switch's documentation from the command line. Loads every
per-package sherlodoc_db.marshal under $OPAM_SWITCH_PREFIX/doc and
queries them together — sherlodoc's API searches a list of databases
and unions the results — so a single query covers the whole switch,
with each result labelled by its owning package. --package restricts
to named packages; -n caps the result count.
A database that fails to load (e.g. written by an incompatible
sherlodoc version) is skipped with a warning rather than aborting the
search.
Adds a dependency on sherlodoc (query + db_store libraries).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A cmdliner CLI with five subcommands: record (hook recorder, one atomic
append), sync (session worker: erase removed, rebuild stale in dependency
order via odoc_driver_opam, regenerate landing page), rebuild (manual
escape hatch), order (debugging aid) and setup (configure the opam wrapper
fields via opam option).
Hook-facing commands always exit 0 so documentation failures never fail an
opam invocation; details go to var/cache/switchdocs/log and failed
packages stay pending for retry by the next session.
Dependency order is computed at sync time from
.opam-switch/packages/*/opam via opam-format, excluding {post} deps and
including depopts. Covered by cram tests using a fake switch layout and a
stub driver; the opam option append syntax used by setup --apply was
verified against a scratch opam root.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>