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
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ codelinks = "sphinx_codelinks.cmd:app"
"docs:rm" = "rm -rf docs/_build/html"
"docs" = "sphinx-build -nW --keep-going -b html -T -c docs docs/source docs/_build/html"
"docs:clean" = { chain = ["docs:rm", "docs"] }
# needextend demo
"analyse" = "codelinks analyse tests/data/configs/minimum_config.toml"
"analyse:rm" = "rm -rf output/marked_content.json"
"write" = "codelinks write rst output/marked_content.json --outpath tests/data/needextend_demo/needextend.rst"
"write:rm" = "rm -rf tests/data/needextend_demo/needextend.rst"
"demo:rm" = "rm -rf tests/data/needextend_demo/_build"
"demo:build" = "sphinx-build -nW --keep-going -b html -T -c tests/data/needextend_demo tests/data/needextend_demo tests/data/needextend_demo/_build/html"
"demo:clean" = { chain = [
"demo:rm",
"analyse:rm",
"write:rm",
"analyse",
"write",
"demo:build",
] }

[tool.ruff.lint]
extend-select = [
Expand Down
4 changes: 2 additions & 2 deletions src/sphinx_codelinks/analyse/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@


class MarkedContentType(str, Enum):
need = ("need",)
need_id_refs = ("need-id-refs",)
need = "need"
need_id_refs = "need-id-refs"
rst = "rst"


Expand Down
105 changes: 104 additions & 1 deletion src/sphinx_codelinks/cmd.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from collections import deque
import json
from os import linesep
from pathlib import Path
import tomllib
from typing import Annotated, cast
from typing import Annotated, TypeAlias, cast

import typer

Expand All @@ -13,6 +14,8 @@
CodeLinksProjectConfigType,
generate_project_configs,
)
from sphinx_codelinks.logger import logger
from sphinx_codelinks.needextend_write import MarkedObjType, convert_marked_content
from sphinx_codelinks.source_discover.config import (
CommentType,
SourceDiscoverConfig,
Expand All @@ -23,6 +26,33 @@
app = typer.Typer(
no_args_is_help=True, context_settings={"help_option_names": ["-h", "--help"]}
)
write_app = typer.Typer(
help="Export marked content to other formats", no_args_is_help=True
)
app.add_typer(write_app, name="write", rich_help_panel="Sub-menus")

OptVerbose: TypeAlias = Annotated[ # noqa: UP040 # has to be TypeAlias
bool,
typer.Option(
...,
"-v",
"--verbose",
is_flag=True,
help="Show debug information",
rich_help_panel="Logging",
),
]
OptQuiet: TypeAlias = Annotated[ # noqa: UP040 # has to be TypeAlias
bool,
typer.Option(
...,
"-q",
"--quiet",
is_flag=True,
help="Only show errors and warnings",
rich_help_panel="Logging",
),
]


@app.command(no_args_is_help=True)
Expand Down Expand Up @@ -190,6 +220,79 @@ def discover(
typer.echo(file_path)


@write_app.command("rst", no_args_is_help=True)
def write_rst( # noqa: PLR0913 # for CLI, so it takes as many as it requires
jsonpath: Annotated[
Path,
typer.Argument(
...,
help="Path of the JSON file which contains the extracted markers",
show_default=False,
dir_okay=False,
file_okay=True,
exists=True,
resolve_path=True,
),
],
outpath: Annotated[
Path,
typer.Option(
"--outpath",
"-o",
help="The output path for generated rst file",
show_default=True,
dir_okay=False,
file_okay=True,
exists=False,
),
] = Path("needextend.rst"),
remote_url_field: Annotated[
str,
typer.Option(
"--remote-url-field",
"-r",
help="The field name for the remote url",
show_default=True,
),
] = "remote_url", # to show default value in this CLI
title: Annotated[
str | None,
typer.Option(
"--title",
"-t",
help="Give the title to the generated RST file",
show_default=True,
),
] = None, # to show default value in this CLI
verbose: OptVerbose = False,
quiet: OptQuiet = False,
) -> None:
"""Generate needextend.rst from the extracted obj in JSON."""
logger.configure(verbose, quiet)
try:
with jsonpath.open("r") as f:
marked_content = json.load(f)
except Exception as e:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raise typer.BadParameter(
f"Failed to load marked content from {jsonpath}: {e}"
) from e

marked_objs: list[MarkedObjType] = [
obj for objs in marked_content.values() for obj in objs
]

needextend_texts, errors = convert_marked_content(
marked_objs, remote_url_field, title
)
if errors:
raise typer.BadParameter(
f"Errors occurred during conversion: {linesep.join(errors)}"
)
with outpath.open("w") as f:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, this can also fail easily - permission issues and stuff

f.writelines(needextend_texts)
typer.echo(f"Generated {outpath}")


def load_config_from_toml(toml_file: Path) -> CodeLinksConfigType:
try:
with toml_file.open("rb") as f:
Expand Down
94 changes: 94 additions & 0 deletions src/sphinx_codelinks/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from rich.console import Console
from rich.text import Text
import typer


class Logger:
__slots__ = ("console", "err_console", "quiet", "verbose")

def __init__(self, *, verbose: bool = False, quiet: bool = False) -> None:
self.verbose = verbose
self.quiet = quiet
self.console = Console()
self.err_console = Console(stderr=True)

def configure(self, verbose: bool = False, quiet: bool = False) -> None:
self.verbose = verbose
self.quiet = quiet

def debug(
self,
*msg: str | Text,
style: str | None = typer.colors.BRIGHT_BLACK,
highlight: bool = False,
markup: bool = False,
console: Console | None = None,
) -> None:
"""Print a debug message.

Will only be shown if verbose mode is enabled and not in quiet mode.
"""
if self.verbose and not self.quiet:
(console or self.console).print(
*msg, style=style, highlight=highlight, markup=markup
)

def info(
self,
*msg: str | Text,
style: str | None = None,
highlight: bool = False,
markup: bool = False,
no_wrap: bool | None = None,
console: Console | None = None,
) -> None:
"""Print an informational message.

Will be suppressed in quiet mode.
"""
if not self.quiet:
(console or self.console).print(
*msg, style=style, highlight=highlight, markup=markup, no_wrap=no_wrap
)

def result(
self,
*msg: str | Text,
style: str | None = None,
highlight: bool = False,
markup: bool = False,
console: Console | None = None,
) -> None:
"""Print a result message, like info but ignores quiet mode."""
(console or self.console).print(
*msg, style=style, highlight=highlight, markup=markup
)

def warning(
self,
*msg: str | Text,
style: str | None = typer.colors.YELLOW,
highlight: bool = False,
markup: bool = False,
console: Console | None = None,
) -> None:
"""Print a warning message."""
(console or self.console).print(
*msg, style=style, highlight=highlight, markup=markup
)

def error(
self,
*msg: str | Text,
style: str | None = typer.colors.RED,
highlight: bool = False,
markup: bool = False,
console: Console | None = None,
) -> None:
"""Print an error message."""
(console or self.err_console).print(
*msg, style=style, highlight=highlight, markup=markup
)


logger = Logger()
Loading
Loading