Skip to content
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: 1 addition & 1 deletion src/docc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
The documentation compiler.
"""

__version__ = "0.5.1"
__version__ = "0.6.0"
"Current version of docc"
12 changes: 8 additions & 4 deletions src/docc/plugins/files.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2023 Ethereum Foundation
# Copyright (C) 2023,2026 Ethereum Foundation
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -131,6 +131,11 @@ class FilesDiscover(Discover):

sources: Sequence[FileSource]

def _file_source(
self, relative_path: PurePath, absolute_path: PurePath
) -> FileSource:
return FileSource(relative_path, absolute_path)

def __init__(self, config: PluginSettings) -> None:
"""
Construct a new instance with the given configuration.
Expand All @@ -140,13 +145,12 @@ def __init__(self, config: PluginSettings) -> None:
self.sources = []
else:
sources = []
self.sources = sources

for item in files:
absolute_path = config.resolve_path(PurePath(item))
relative_path = config.unresolve_path(absolute_path)
sources.append(FileSource(relative_path, absolute_path))

self.sources = sources
sources.append(self._file_source(relative_path, absolute_path))

def discover(self, known: FrozenSet[T]) -> Iterator[Source]:
"""
Expand Down
14 changes: 9 additions & 5 deletions src/docc/plugins/html/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
Plugin that renders to HTML.
"""


import html.parser
import sys
import xml.etree.ElementTree as ET
Expand Down Expand Up @@ -144,26 +143,31 @@ def __init__(self, config: PluginSettings) -> None:
Construct a new instance with the given configuration.
"""

def _resource_source_with_path(
self, mod: str, input_path: PurePath, output_path: PurePath
) -> ResourceSource:
return ResourceSource.with_path(mod, input_path, output_path)

def discover(self, known: FrozenSet[T]) -> Iterator[Source]:
"""
Find sources.
"""
yield ResourceSource.with_path(
yield self._resource_source_with_path(
"docc.plugins.html",
PurePath("static") / "chota" / "dist" / "chota.min.css",
PurePath("static") / "chota",
)
yield ResourceSource.with_path(
yield self._resource_source_with_path(
"docc.plugins.html",
PurePath("static") / "docc.css",
PurePath("static") / "docc",
)
yield ResourceSource.with_path(
yield self._resource_source_with_path(
"docc.plugins.html",
PurePath("static") / "fuse" / "dist" / "fuse.min.js",
PurePath("static") / "fuse",
)
yield ResourceSource.with_path(
yield self._resource_source_with_path(
"docc.plugins.html",
PurePath("static") / "search.js",
PurePath("static") / "search",
Expand Down
98 changes: 67 additions & 31 deletions src/docc/plugins/listing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
Plugin that renders directory listings.
"""

from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Iterable
from os.path import commonpath
Expand All @@ -29,6 +28,7 @@
Final,
FrozenSet,
Iterator,
Optional,
Set,
Tuple,
Type,
Expand All @@ -50,7 +50,7 @@
from docc.transform import Transform


class Listable(ABC):
class Listable:
"""
Mixin to change visibility of a Source in a directory listing.
"""
Expand All @@ -63,7 +63,7 @@ def _sorting_key(
return thing.listing_order_key()
elif isinstance(thing, Source):
path = thing.relative_path or thing.output_path
return (True, path, None)
return (Listable._index_dir(thing) is None, path, None)
return (True, None, thing)

@staticmethod
Expand All @@ -76,18 +76,27 @@ def _show_source(source: Source) -> bool:

return True

@staticmethod
def _index_dir(source: Source) -> Optional[PurePath]:
if isinstance(source, Listable):
return source.index_dir
return None

@property
@abstractmethod
def show_in_listing(self) -> bool:
def index_dir(self) -> Optional[PurePath]:
"""
`True` if this `Source` should be shown in directory listings.
For index sources, the directory the source indexes. For other sources,
`None`.

For example, for an output path of `./foo/index`, this should return
`./foo`.
"""
raise NotImplementedError()
return None

@property
def is_leaf(self) -> bool:
def show_in_listing(self) -> bool:
"""
`True` if this `Source` cannot contain child sources (eg. a file).
`True` if this `Source` should be shown in directory listings.
"""
return True

Expand All @@ -99,8 +108,8 @@ def listing_order_key(
"""
if isinstance(self, Source):
path = self.relative_path or self.output_path
return (self.is_leaf, path, None)
return (self.is_leaf, None, self)
return (self.index_dir is None, path, None)
return (True, None, self)


class ListingDiscover(Discover):
Expand All @@ -111,15 +120,20 @@ class ListingDiscover(Discover):
def __init__(self, config: PluginSettings) -> None:
pass

def _index_path(self, parent: PurePath) -> PurePath:
return parent / "index"

def _listing_source(
self, source: Source, parent: PurePath
) -> "ListingSource":
return ListingSource(parent, parent / "index", set())
return ListingSource(parent, self._index_path(parent))

def discover(self, known: FrozenSet[T]) -> Iterator["ListingSource"]:
"""
Find sources.
"""
output_paths = {s.output_path: s for s in known}

listings = {}

for source in known:
Expand All @@ -134,11 +148,14 @@ def discover(self, known: FrozenSet[T]) -> Iterator["ListingSource"]:
try:
listing = listings[parent]
except KeyError:
listing = self._listing_source(source, parent)
listings[parent] = listing
yield listing
index_path = self._index_path(parent)
try:
listing = output_paths[index_path]
except KeyError:
listing = self._listing_source(source, parent)
listings[parent] = listing
yield listing

listing.sources.add(source)
source = listing


Expand All @@ -158,8 +175,13 @@ def add_source(self, source: Source) -> None:
"""
Register a source.
"""
path = source.relative_path or source.output_path
self.sources[path.parent].add(source)
index_dir = Listable._index_dir(source)
if index_dir is None:
path = source.relative_path or source.output_path
self.sources[path.parent].add(source)
else:
self.sources[index_dir].add(source)
self.sources[index_dir.parent].add(source)

def descendants(self, source: Source) -> Iterable[Source]:
"""
Expand Down Expand Up @@ -215,19 +237,13 @@ class ListingSource(Source, Listable):

_relative_path: Final[PurePath]
_output_path: Final[PurePath]
sources: Final[Set[Source]]

show_in_listing: bool = True
is_leaf: bool = False

def __init__(
self,
relative_path: PurePath,
output_path: PurePath,
sources: Set[Source],
) -> None:
self._relative_path = relative_path
self.sources = sources
self._output_path = output_path

@property
Expand All @@ -244,6 +260,17 @@ def relative_path(self) -> PurePath:
"""
return self._relative_path

@property
def index_dir(self) -> PurePath:
"""
For index sources, the directory the source indexes. For other sources,
`None`.

For example, for an output path of `./foo/index`, this should return
`./foo`.
"""
return self.output_path.parent


class ListingBuilder(Builder):
"""
Expand Down Expand Up @@ -339,6 +366,8 @@ def render_html(
sources = sorted(sources, key=Listable._sorting_key)

output_path = context[Source].output_path
output_parent = output_path.parent

entries = []

for source in sources:
Expand All @@ -360,13 +389,20 @@ def render_html(
) # TODO: Don't hardcode extension.

active = source is context[Source]
path = source.relative_path or source.output_path

if node.leaf:
path = path.name

if isinstance(source, Listable) and not source.is_leaf:
path = str(path) + "/"
entry_index = Listable._index_dir(source)
relative = source.relative_path or source.output_path

if entry_index is None or (
entry_index == output_parent and relative != entry_index
):
# Regular source, or an index source (like `__init__.py`) appearing
# in its own listing. Show as `<name>`.
path = relative.name if node.leaf else str(relative)
else:
# Synthetic listing, or a file-based index (like `__init__.py`)
# appearing in its parent directory's listing. Show as `<dir>/`.
path = entry_index.name if node.leaf else str(entry_index)
path = path + "/"

entries.append((path, relative_path, active))

Expand Down
35 changes: 32 additions & 3 deletions src/docc/plugins/python/cst.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from docc.context import Context
from docc.discover import Discover, T
from docc.document import BlankNode, Document, ListNode, Node, Visit, Visitor
from docc.plugins.listing import ListingNode
from docc.plugins.listing import Listable, ListingNode
from docc.plugins.references import Definition, Reference
from docc.plugins.verbatim import Fragment, Pos, Verbatim
from docc.settings import PluginSettings
Expand Down Expand Up @@ -87,6 +87,14 @@ class PythonDiscover(Discover):

settings: PluginSettings

def _python_source(
self,
root_path: PurePath,
relative_path: PurePath,
absolute_path: PurePath,
) -> "PythonSource":
return PythonSource(root_path, relative_path, absolute_path)

def __init__(self, config: PluginSettings) -> None:
self.settings = config

Expand Down Expand Up @@ -127,10 +135,12 @@ def discover(self, known: FrozenSet[T]) -> Iterator[Source]:

parents = relative_path.parents
if not any(p in parents for p in self.excluded_paths):
yield PythonSource(root_path, relative_path, absolute_path)
yield self._python_source(
root_path, relative_path, absolute_path
)


class PythonSource(TextSource):
class PythonSource(TextSource, Listable):
"""
A Source representing a Python file.
"""
Expand All @@ -149,6 +159,9 @@ def __init__(
self._relative_path = relative_path
self.absolute_path = absolute_path

def _is_init(self) -> bool:
return self._relative_path.name == "__init__.py"

@property
def relative_path(self) -> Optional[PurePath]:
"""
Expand All @@ -161,6 +174,9 @@ def output_path(self) -> PurePath:
"""
Where to put the output derived from this source.
"""
if self._is_init():
return self._relative_path.with_name("index")

return self._relative_path

def open(self) -> TextIO:
Expand All @@ -169,6 +185,19 @@ def open(self) -> TextIO:
"""
return open(self.absolute_path, "r")

@property
def index_dir(self) -> Optional[PurePath]:
"""
For index sources, the directory the source indexes. For other sources,
`None`.

For example, for an output path of `./foo/index`, this should return
`./foo`.
"""
if self._is_init():
return self.output_path.parent
return None


class PythonBuilder(Builder):
"""
Expand Down
Loading