mirror of
https://github.com/qdrant/landing_page.git
synced 2026-09-25 22:18:30 +02:00
202 lines
6.4 KiB
Python
202 lines
6.4 KiB
Python
import dataclasses
|
|
import os
|
|
import re
|
|
import typing
|
|
from pathlib import Path
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from _typeshed import StrOrBytesPath
|
|
|
|
|
|
class Language:
|
|
"""Base class for a programming language snippet tester."""
|
|
|
|
NAME: str
|
|
"""Name of the programming language, e.g., 'python'."""
|
|
|
|
SNIPPET_FILENAME: str
|
|
"""Filename of the snippet in this language, e.g., 'python.py'."""
|
|
|
|
SUPPORTS_SYNTAX_CHECK: bool = False
|
|
"""Whether `check_syntax()` is implemented for this language."""
|
|
|
|
@classmethod
|
|
def compile(cls, tmpdir: Path, fnames: list[Path]) -> "CompileResult":
|
|
"""Compile, typecheck, and prepare all snippets listed in fnames.
|
|
|
|
Might create scratch projects in tmpdir.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def check_syntax(cls, code: str) -> None:
|
|
"""Parse (but do not compile/typecheck) code, raising an exception
|
|
if it is not syntactically valid.
|
|
|
|
Unlike `compile()`, this does not need to resolve imports/types or
|
|
build a scratch project, so it can run directly on the shortened
|
|
code extracted from generated markdown files.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def shorten(cls, contents: str) -> dict[str, str]:
|
|
"""Shorten the snippet contents into a form suitable for inclusion in
|
|
documentation. Also splits the snippet into blocks.
|
|
|
|
Removes boilerplate code, e.g. class wrappers, main functions, etc.
|
|
Returns mapping `block_name` -> `block_contents`.
|
|
"""
|
|
return generic_shorten(contents)
|
|
|
|
@classmethod
|
|
def unshorten(cls, contents: str) -> str:
|
|
"""Convert the shortened snippet into runnable code.
|
|
|
|
Not reliable, might require manual adjustments.
|
|
"""
|
|
return contents
|
|
|
|
@classmethod
|
|
def format(cls, fnames: list[str]) -> None:
|
|
"""Format the snippets specified by fnames in place."""
|
|
pass
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class CompileResult:
|
|
run_args: dict[Path, typing.Sequence["StrOrBytesPath"]] = dataclasses.field(
|
|
default_factory=dict
|
|
)
|
|
"""Mapping from the snippet filename to the command that runs the
|
|
snippet."""
|
|
|
|
has_issues: bool = False
|
|
"""If true, indicates that there were issues during
|
|
compilation/typechecking, but the snippets can still be run.
|
|
"""
|
|
|
|
|
|
def copy_template(src: Path, dst: Path) -> None:
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
dst.write_text(src.read_text().replace("{collection_name}", "collection_name"))
|
|
|
|
|
|
def template(
|
|
original_fname: Path,
|
|
target_fname: Path,
|
|
replacements: dict[str, str],
|
|
) -> None:
|
|
result = []
|
|
for line in original_fname.read_text().splitlines(keepends=True):
|
|
if (r := replacements.get(line.strip())) is not None:
|
|
result.append(r + "\n")
|
|
else:
|
|
result.append(line)
|
|
target_fname.write_text("".join(result))
|
|
|
|
|
|
_RE_COMMENT = re.compile(
|
|
r"""
|
|
^
|
|
(?P<code> .*\s | ) # code before comment
|
|
(?: // | \# ) # comment start
|
|
\s*
|
|
(?P<annotation> @\S+ )
|
|
(?P<param> \s+ .* )?
|
|
$
|
|
""",
|
|
re.VERBOSE,
|
|
)
|
|
|
|
|
|
def generic_shorten(text: str) -> dict[str, str]:
|
|
"""Generic implementation of Language.shorten().
|
|
|
|
Processes annotation comments (@hide, @block-start, etc.), trims excessive
|
|
newlines, and splits into blocks.
|
|
"""
|
|
blocks = {
|
|
# empty string is the default block (does not live in a subdirectory)
|
|
"": []
|
|
}
|
|
current_blocks = [""]
|
|
hide_mode = False
|
|
for line in text.splitlines():
|
|
if (m := _RE_COMMENT.match(line)) is None:
|
|
if not hide_mode:
|
|
for block in current_blocks:
|
|
blocks[block].append(line + "\n")
|
|
continue
|
|
|
|
has_code = m["code"].strip() != ""
|
|
annotation = m["annotation"]
|
|
param = m["param"].strip() if m["param"] is not None else ""
|
|
if annotation == "@hide":
|
|
if not has_code:
|
|
raise ValueError("Hiding empty line is not allowed")
|
|
if hide_mode:
|
|
raise ValueError("@hide inside @hide-start/@hide-end is not allowed")
|
|
if param:
|
|
raise ValueError("@hide should not be followed by any parameters")
|
|
elif annotation == "@hide-start":
|
|
if has_code:
|
|
raise ValueError("@hide-start should be on its own line")
|
|
if hide_mode:
|
|
raise ValueError("Nesting @hide-start is not allowed")
|
|
if param:
|
|
raise ValueError("@hide-start should not be followed by any parameters")
|
|
hide_mode = True
|
|
elif annotation == "@hide-end":
|
|
if has_code:
|
|
raise ValueError("@hide-end should be on its own line")
|
|
if not hide_mode:
|
|
raise ValueError("@hide-end without matching @hide-start")
|
|
if param:
|
|
raise ValueError("@hide-end should not be followed by any parameters")
|
|
hide_mode = False
|
|
elif annotation == "@block-start":
|
|
if has_code:
|
|
raise ValueError("@block-start should be on its own line")
|
|
if param:
|
|
current_blocks.append(param)
|
|
blocks[param] = []
|
|
else:
|
|
raise ValueError("@block-start should be followed by block name")
|
|
elif annotation == "@block-end":
|
|
if has_code:
|
|
raise ValueError("@block-end should be on its own line")
|
|
if param:
|
|
current_blocks.remove(param)
|
|
else:
|
|
raise ValueError("@block-end should be followed by block name")
|
|
else:
|
|
raise ValueError(f"Unknown annotation: {annotation}")
|
|
if hide_mode:
|
|
raise ValueError("Unclosed @hide-start")
|
|
|
|
snippets = {}
|
|
for key in blocks.keys():
|
|
text = "".join(blocks[key])
|
|
text = text.lstrip("\n").rstrip("\n")
|
|
text = re.sub(r"\n{3,}", "\n\n", text)
|
|
if text:
|
|
text += "\n"
|
|
snippets[key] = text
|
|
return snippets
|
|
|
|
|
|
def trim_commonpath(fnames: list[Path]) -> dict[Path, Path]:
|
|
"""Trim the common path prefix from a list of file paths.
|
|
|
|
Returns a mapping from original paths to trimmed paths.
|
|
"""
|
|
common_prefix = os.path.commonpath(
|
|
[(Path.cwd() / fname.parent) for fname in fnames]
|
|
)
|
|
result = {}
|
|
for fname in fnames:
|
|
trimmed = Path(os.path.relpath(fname, common_prefix))
|
|
result[fname] = trimmed
|
|
return result
|