mirror of
https://github.com/qdrant/landing_page.git
synced 2026-10-02 17:38:31 +02:00
Snippet tools
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
.idea
|
||||
__pycache__
|
||||
qdrant-landing/.DS_Store
|
||||
.DS_Store
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.gradle
|
||||
/cache
|
||||
/clients
|
||||
/tmp
|
||||
@@ -0,0 +1,116 @@
|
||||
# Snippet tools
|
||||
|
||||
This is a tool to work with code snippets.
|
||||
|
||||
1. Automated CI type checking of snippets.
|
||||
2. Writing new snippets and checking/running/testing them locally.
|
||||
This tool hides the complexity of setting up every build system/dev env for each client.
|
||||
Also, it supports testing snippets against unreleased versions of clients (from git branches/PRs).
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
To convert runnable snippets into markdown ([`generate-md.py`](./generate-md.py)) you need only python with no extra dependencies.
|
||||
|
||||
To typecheck/build snippets, you need a bunch of tools installed.
|
||||
The all-in-one [`Dockerfile`](./docker/Dockerfile) and [`shell.nix`](./shell.nix) are provided.
|
||||
Run [`./docker.sh`](./docker.sh) to start a shell in the docker container.
|
||||
|
||||
On ARM-based macOS, ensure that the Docker Desktop setting "General" > "Virtual Machine Options" > "Use Rosetta for x86\_64/amd64 emulation on Apple Silicon" is selected.
|
||||
Note: on macOS, if you see Java compile failures related to `protobuf`, re-running the test will often solve those.
|
||||
|
||||
|
||||
## Workflow: typechecking and running
|
||||
|
||||
Clone and build clients (latest git versions).
|
||||
```bash
|
||||
./sync-clients.py fetch --csharp --java --typescript
|
||||
```
|
||||
|
||||
(optional) You might specify particular tags/branches/commits instead.
|
||||
```bash
|
||||
./sync-clients.py fetch --csharp=1.16.0 --java=b290b14892534fc0d76893d41b8f656855cd589b --typescript=main
|
||||
```
|
||||
|
||||
(optional) Update the lockfiles for templates (see ./sync-clients.py lock --help for syntax).
|
||||
```bash
|
||||
./sync-clients.py lock --go=v1.16.0 --rust=branch=master --python=tag=v1.16.0
|
||||
```
|
||||
|
||||
Typecheck/build all snippets or a particular language/snippet.
|
||||
```bash
|
||||
./check.py build
|
||||
./check.py build -l csharp,java
|
||||
./check.py build ./snippets/create-collection/simple
|
||||
./check.py build ./snippets/create-collection/simple/go.go
|
||||
```
|
||||
|
||||
Test all testable snippets against localhost Qdrant instance. (you start it yourself).
|
||||
Testable snippets are the ones that have `test.py` file in the same directory.
|
||||
```bash
|
||||
./check.py test
|
||||
```
|
||||
|
||||
Run a particular snippet without running a test.
|
||||
```bash
|
||||
./check.py run ./snippets/create-collection/simple/go.go
|
||||
```
|
||||
|
||||
|
||||
## Workflow: writing new snippets/converting old ones
|
||||
|
||||
Convert existing old markdown snippets.
|
||||
```bash
|
||||
./migrate-snippet.py ./snippets/create-collection/simple/go.md
|
||||
```
|
||||
Or create new snippets from scratch.
|
||||
|
||||
To reduce noise, you can hide boilerplate code using special comment:
|
||||
```go
|
||||
if err != nil { panic(err) } // @hide
|
||||
```
|
||||
The usual wrapping boileplate like `public static void run() throws Exception {` is already hidden by default.
|
||||
|
||||
Typecheck/build the snippet.
|
||||
```bash
|
||||
./check.py build ./snippets/create-collection/simple
|
||||
```
|
||||
|
||||
(Optional) Start Qdrant and run the snippet.
|
||||
```bash
|
||||
./check.py run ./snippets/create-collection/simple/go.go
|
||||
```
|
||||
|
||||
(Optional) Write `test.py` and test it.
|
||||
```bash
|
||||
./check.py test ./snippets/create-collection/simple
|
||||
```
|
||||
|
||||
Before committing, regenerate markdown files.
|
||||
If migrated, also delete old markdown files.
|
||||
```bash
|
||||
./generate-md.py
|
||||
git rm ./snippets/create-collection/simple/go.md # if migrated
|
||||
git add ./snippets/create-collection/simple/generated/go.md # made by generate-md.py
|
||||
git add ./snippets/create-collection/simple/go.go
|
||||
```
|
||||
|
||||
|
||||
## Architecture
|
||||
|
||||
For each language, the snippet tester collects all code snippets from the [`snippets/`](../../qdrant-landing/content/documentation/headless/snippets) directory and puts them in a single scratch project in a temporary directory.
|
||||
Then those projects are compiled/typechecked.
|
||||
Putting all snippets in a single project rather than compiling them one by one saves time: some build systems like `gradle` take a lot of time to spin up.
|
||||
|
||||
Each supported language has:
|
||||
- A template project in [`templates/`](./templates) directory.
|
||||
- A Python subclass of `Language` in [`lib/languages/`](./lib/languages). It is responsible for:
|
||||
- Creating the scratch project from snippets and the template.
|
||||
- Building/typechecking the project.
|
||||
- Running a particular snippet for testing.
|
||||
- A list of build dependencies in [`docker/Dockerfile`](./docker/Dockerfile) and [`shell.nix`](./shell.nix).
|
||||
- A handler in [`sync-clients.py`](./sync-clients.py) to fetch the particular git revision of the client.
|
||||
- For Java, C#, Typescript it fetches the client sources to the `clients/` directory, and builds them.
|
||||
This directory is gitignored.
|
||||
- For Go, Rust, Python it updates the lockfile in the [`templates/`](./templates) directory.
|
||||
These lockfiles are checked in this repo.
|
||||
Executable
+174
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
from lib import SNIPPETS_DIR, CollectedSnippetsType, collect_snippets, log
|
||||
from lib.languages import ALL_LANGUAGES, CompileResult, Language, parse_languages
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
default_langs = ",".join([lang.NAME for lang in ALL_LANGUAGES])
|
||||
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--langs",
|
||||
help=f"comma-separated list of languages, default is {default_langs}",
|
||||
default=default_langs,
|
||||
)
|
||||
parser.add_argument(
|
||||
"command",
|
||||
help="command to execute",
|
||||
choices=["build", "run", "test"],
|
||||
)
|
||||
parser.add_argument(
|
||||
"snippets",
|
||||
nargs="*",
|
||||
help=f"Snippet filenames/directories to process, default: {SNIPPETS_DIR}",
|
||||
default=[SNIPPETS_DIR],
|
||||
type=Path,
|
||||
metavar="SNIPPET_PATH",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
languages = parse_languages(args.langs)
|
||||
snippets = collect_snippets(args.snippets, languages)
|
||||
assert args.command in ("build", "run", "test")
|
||||
|
||||
# Q: Why not `tempfile.TemporaryDirectory()`?
|
||||
# A: Harder to debug/inspect generated files.
|
||||
tmpdir = Path(__file__).parent / "tmp"
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
tmpdir.mkdir(parents=True)
|
||||
build_and_run(tmpdir=tmpdir, snippets=snippets, mode=args.command)
|
||||
|
||||
|
||||
def build_and_run(
|
||||
tmpdir: Path,
|
||||
snippets: CollectedSnippetsType,
|
||||
mode: typing.Literal[
|
||||
"build", # just build
|
||||
"run", # build and run each snippet
|
||||
"test", # build, then run/test all snippets that have a test.py file
|
||||
],
|
||||
) -> None:
|
||||
snippets_by_lang: dict[type[Language], list[Path]] = {}
|
||||
|
||||
for snippets2 in snippets.values():
|
||||
for lang, fname in snippets2.items():
|
||||
snippets_by_lang.setdefault(lang, []).append(fname)
|
||||
|
||||
compile_results: dict[type[Language], CompileResult] = {}
|
||||
errors: list[str] = []
|
||||
|
||||
# Load test modules before compilation.
|
||||
# We want them to crash early.
|
||||
test_modules: dict[Path, types.ModuleType] = {}
|
||||
if mode == "test":
|
||||
for snippet_dir in snippets:
|
||||
test_file = snippet_dir / "test.py"
|
||||
if not test_file.exists():
|
||||
continue
|
||||
spec = importlib.util.spec_from_file_location("test_module", test_file)
|
||||
assert spec is not None
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
spec.loader.exec_module(mod)
|
||||
test_modules[snippet_dir] = mod
|
||||
|
||||
log("Compile stage")
|
||||
for lang, fnames in snippets_by_lang.items():
|
||||
log(f"· Compiling {lang.NAME} snippets")
|
||||
try:
|
||||
res = lang.compile(tmpdir / lang.NAME, fnames)
|
||||
if res.has_issues:
|
||||
log(f"· · Compilation had issues for {lang.NAME}")
|
||||
errors.append(f"Compilation had issues for {lang.NAME}")
|
||||
compile_results[lang] = res
|
||||
except Exception as e:
|
||||
log(f"· · Compilation failed for {lang.NAME}")
|
||||
errors.append(f"Compilation failed for {lang.NAME}")
|
||||
if not isinstance(e, subprocess.CalledProcessError):
|
||||
traceback.print_exc()
|
||||
|
||||
if mode in ("run", "test"):
|
||||
log("Run stage")
|
||||
for snippet_dir, snippets2 in snippets.items():
|
||||
if mode == "run":
|
||||
for lang, snippet_fname in snippets2.items():
|
||||
if (compile_result := compile_results.get(lang)) is None:
|
||||
continue
|
||||
|
||||
log(f"· Running {snippet_fname}")
|
||||
p = subprocess.run(
|
||||
compile_result.run_args[snippet_fname],
|
||||
text=True,
|
||||
)
|
||||
if p.returncode != 0:
|
||||
log(f"· · Exit code {p.returncode}")
|
||||
|
||||
if mode == "test" and snippet_dir in test_modules:
|
||||
log(f"· Testing snippets in {snippet_dir}")
|
||||
mod = test_modules[snippet_dir]
|
||||
|
||||
for lang, snippet_fname in snippets2.items():
|
||||
compile_result = compile_results.get(lang)
|
||||
if compile_result is None:
|
||||
continue
|
||||
|
||||
log(f"· · Testing {snippet_fname}")
|
||||
|
||||
output = None
|
||||
try:
|
||||
if hasattr(mod, "prepare"):
|
||||
mod.prepare()
|
||||
|
||||
p = subprocess.run(
|
||||
compile_result.run_args[snippet_fname],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
|
||||
output = p.stdout
|
||||
if p.returncode != 0:
|
||||
msg = f"Process exited with code {p.returncode}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
if hasattr(mod, "check"):
|
||||
mod.check()
|
||||
except Exception:
|
||||
log(f"· · · Testing {snippet_fname} failed")
|
||||
if output:
|
||||
print(output.rstrip())
|
||||
traceback.print_exc()
|
||||
errors.append(f"Testing {snippet_fname} failed")
|
||||
finally:
|
||||
try:
|
||||
if hasattr(mod, "cleanup"):
|
||||
mod.cleanup()
|
||||
except Exception:
|
||||
log(f"· · · Teardown for {snippet_fname} failed")
|
||||
errors.append(f"Teardown for {snippet_fname} failed")
|
||||
traceback.print_exc()
|
||||
|
||||
if errors:
|
||||
log("Errors encountered:")
|
||||
for err in errors:
|
||||
log(f"· {err}")
|
||||
sys.exit(1)
|
||||
elif mode == "test":
|
||||
log("All tests passed successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -e
|
||||
|
||||
IMAGE_NAME="snippet-checker"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
REPO_ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# check if on macos
|
||||
|
||||
if ! docker image inspect "$IMAGE_NAME" > /dev/null 2>&1; then
|
||||
# TODO: comment why
|
||||
[ "$(uname)" != Darwin ] || export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
||||
docker build -t "$IMAGE_NAME" "$SCRIPT_DIR/docker"
|
||||
fi
|
||||
|
||||
[ $# -ne 0 ] || set -- bash
|
||||
|
||||
[ -t 0 ] && TTY_FLAG=--tty || TTY_FLAG=
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
$TTY_FLAG \
|
||||
--network host \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$REPO_ROOT_DIR:/workspace" \
|
||||
"$IMAGE_NAME" \
|
||||
uv run "$@"
|
||||
@@ -0,0 +1,51 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
git \
|
||||
software-properties-common \
|
||||
# for rust client
|
||||
gcc \
|
||||
libc6-dev \
|
||||
# for go client
|
||||
golang \
|
||||
# for java client
|
||||
openjdk-21-jdk-headless \
|
||||
# for python client
|
||||
python3 \
|
||||
# for typescript client
|
||||
nodejs \
|
||||
npm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# for csharp client
|
||||
RUN add-apt-repository ppa:dotnet/backports && \
|
||||
apt-get update && \
|
||||
apt-get install -y dotnet-sdk-8.0 --no-install-recommends && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# for rust client
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
||||
CARGO_HOME=/opt/rust RUSTUP_HOME=/opt/rust \
|
||||
sh -s -- -y --default-toolchain 1.91.0
|
||||
|
||||
# for typescript client
|
||||
RUN npm install --global --prefix /usr/local pnpm
|
||||
# Without this, typescript snippets tries to connect to ::1 instead of 127.1
|
||||
RUN echo "precedence ::ffff:0:0/96 100" >> /etc/gai.conf
|
||||
|
||||
# for python client
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
ENV RUSTUP_HOME=/opt/rust \
|
||||
PATH="/opt/rust/bin:${PATH}"
|
||||
|
||||
# /workspace is the repo root
|
||||
WORKDIR /workspace/automation/snippets
|
||||
|
||||
# Build tools put their caches in $HOME
|
||||
ENV HOME=/workspace/automation/snippets/cache \
|
||||
# gradle ignores $HOME
|
||||
GRADLE_USER_HOME=/workspace/automation/snippets/cache/.gradle
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "-c", "mkdir -p $HOME && \"$@\"", "--"]
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Converts all runnable snippets to markdown files.
|
||||
|
||||
E.g., converts
|
||||
`snippets/create-collection/simple/python.py`
|
||||
to
|
||||
`snippets/create-collection/simple/generated/python.md`
|
||||
|
||||
For using on CI and running before committing changes.
|
||||
The reverse is `./migrate-snippet.py`.
|
||||
"""
|
||||
|
||||
import difflib
|
||||
import shutil
|
||||
import sys
|
||||
import traceback
|
||||
import typing
|
||||
|
||||
from lib import SNIPPETS_DIR, collect_snippets
|
||||
from lib.languages import ALL_LANGUAGES
|
||||
|
||||
|
||||
def main() -> None:
|
||||
snippets = collect_snippets(bases=[SNIPPETS_DIR], languages=ALL_LANGUAGES)
|
||||
|
||||
issues = 0
|
||||
|
||||
for snippet_dir, snippets2 in snippets.items():
|
||||
generated_dir = snippet_dir / "generated"
|
||||
|
||||
shutil.rmtree(generated_dir, ignore_errors=True)
|
||||
generated_dir.mkdir()
|
||||
|
||||
for lang, snippet_fname in snippets2.items():
|
||||
try:
|
||||
shortened = lang.shorten(snippet_fname.read_text())
|
||||
except Exception as e:
|
||||
issues += 1
|
||||
print(f"Warning: failed to shorten snippet {snippet_fname}: {e}")
|
||||
traceback.print_exc()
|
||||
continue
|
||||
generated = f"```{lang.NAME}\n{shortened}```\n"
|
||||
|
||||
generated_fname = generated_dir / f"{lang.NAME}.md"
|
||||
handwritten_fname = snippet_dir / f"{lang.NAME}.md"
|
||||
generated_fname.write_text(generated)
|
||||
|
||||
if handwritten_fname.exists():
|
||||
issues += 1
|
||||
print(
|
||||
"Warning: both snippet and generated file exist:",
|
||||
snippet_dir / f"{lang.NAME}.md",
|
||||
)
|
||||
handwritten = (snippet_dir / f"{lang.NAME}.md").read_text()
|
||||
if handwritten != generated:
|
||||
print_and_colorize_diff(
|
||||
difflib.unified_diff(
|
||||
handwritten.splitlines(keepends=True),
|
||||
generated.splitlines(keepends=True),
|
||||
fromfile=str(handwritten_fname),
|
||||
tofile=str(generated_fname),
|
||||
),
|
||||
)
|
||||
print()
|
||||
|
||||
if issues:
|
||||
print(f"Total issues found: {issues}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def print_and_colorize_diff(diff: typing.Iterable[str]) -> None:
|
||||
for line in diff:
|
||||
if line.startswith("+"):
|
||||
print(end=f"\x1b[32m{line}\x1b[0m", file=sys.stderr)
|
||||
elif line.startswith("-"):
|
||||
print(end=f"\x1b[31m{line}\x1b[0m", file=sys.stderr)
|
||||
elif line.startswith("@"):
|
||||
print(end=f"\x1b[36m{line}\x1b[0m", file=sys.stderr)
|
||||
else:
|
||||
print(end=line, file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,74 @@
|
||||
import os
|
||||
import sys
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
from .languages import Language
|
||||
|
||||
SNIPPETS_DIR = Path(os.path.normpath(Path(__file__) / "../../snippets")).relative_to(
|
||||
Path.cwd()
|
||||
)
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
"""Print a message.
|
||||
|
||||
Use this instead of `print()` to make the output more visible compared to
|
||||
the output of builders/snippet runs.
|
||||
"""
|
||||
print(f"\x1b[30;43m + \x1b[m {msg}", file=sys.stderr)
|
||||
|
||||
|
||||
type CollectedSnippetsType = dict[
|
||||
Path, # directory path
|
||||
dict[type[Language], Path], # language -> snippet path
|
||||
]
|
||||
"""Snippet filenames grouped by directory then by language.
|
||||
|
||||
Example:
|
||||
{
|
||||
Path("snippets/example1"): {
|
||||
LanguageGo: Path("snippets/example1/go.go"),
|
||||
LanguagePython: Path("snippets/example1/python.py"),
|
||||
},
|
||||
Path("snippets/example2"): {
|
||||
LanguageGo: Path("snippets/example2/go.go"),
|
||||
LanguagePython: Path("snippets/example2/python.py"),
|
||||
LanguageRust: Path("snippets/example2/rust.rs"),
|
||||
},
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def collect_snippets(
|
||||
bases: list[Path],
|
||||
languages: typing.Sequence[type[Language]],
|
||||
) -> CollectedSnippetsType:
|
||||
"""Locate snippets in the given base paths."""
|
||||
|
||||
result: CollectedSnippetsType = {}
|
||||
|
||||
for base in bases:
|
||||
found = False
|
||||
if base.is_file():
|
||||
for lang in languages:
|
||||
if base.name == lang.SNIPPET_FILENAME:
|
||||
result.setdefault(base.parent, {})[lang] = base
|
||||
found = True
|
||||
break
|
||||
elif base.is_dir():
|
||||
for root, dirs, files in os.walk(base):
|
||||
dirs[:] = [d for d in dirs if d != "__pycache__"]
|
||||
dirs.sort()
|
||||
dir_path = Path(root)
|
||||
|
||||
for lang in languages:
|
||||
if lang.SNIPPET_FILENAME in files:
|
||||
found = True
|
||||
result.setdefault(dir_path, {})[lang] = (
|
||||
dir_path / lang.SNIPPET_FILENAME
|
||||
)
|
||||
if not found:
|
||||
log(f"Warning: No snippets found in {base}")
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,53 @@
|
||||
from .base import CompileResult, Language
|
||||
from .bash import LanguageBash
|
||||
from .csharp import LanguageCsharp
|
||||
from .go import LanguageGo
|
||||
from .java import LanguageJava
|
||||
from .python import LanguagePython
|
||||
from .rust import LanguageRust
|
||||
from .typescript import LanguageTypescript
|
||||
|
||||
__all__ = [
|
||||
"ALL_LANGUAGES",
|
||||
"CompileResult",
|
||||
"Language",
|
||||
"LanguageBash",
|
||||
"LanguageCsharp",
|
||||
"LanguageGo",
|
||||
"LanguageJava",
|
||||
"LanguagePython",
|
||||
"LanguageRust",
|
||||
"LanguageTypescript",
|
||||
"parse_languages",
|
||||
]
|
||||
|
||||
ALL_LANGUAGES: tuple[type[Language], ...] = (
|
||||
LanguageBash,
|
||||
LanguageCsharp,
|
||||
LanguageGo,
|
||||
LanguageJava,
|
||||
LanguagePython,
|
||||
LanguageRust,
|
||||
LanguageTypescript,
|
||||
)
|
||||
|
||||
|
||||
def parse_languages(langs: str) -> list[type[Language]]:
|
||||
"""Parse comma-separated list of languages into a list of Language classes.
|
||||
|
||||
Accepts either language names or snippet file extensions.
|
||||
Example: "python,ts,go" -> [LanguagePython, LanguageTypescript, LanguageGo]
|
||||
"""
|
||||
result: list[type[Language]] = []
|
||||
for lang_name in langs.split(","):
|
||||
lang_name = lang_name.strip()
|
||||
for lang in ALL_LANGUAGES:
|
||||
if lang in result:
|
||||
continue
|
||||
if lang_name in (lang.NAME, lang.SNIPPET_FILENAME.split(".")[1]):
|
||||
result.append(lang)
|
||||
break
|
||||
else:
|
||||
msg = f"Unknown language: {lang_name}"
|
||||
raise ValueError(msg)
|
||||
return result
|
||||
@@ -0,0 +1,121 @@
|
||||
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'."""
|
||||
|
||||
@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 shorten(cls, contents: str) -> str:
|
||||
"""Shorten the snippet contents into a form suitable for inclusion in
|
||||
documentation.
|
||||
|
||||
Removes boilerplate code, e.g. class wrappers, main functions, etc.
|
||||
"""
|
||||
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".*\s(?://|#)\s*(@.*)$")
|
||||
|
||||
|
||||
def generic_shorten(text: str) -> str:
|
||||
"""Generic implementation of Language.shorten().
|
||||
|
||||
Removes comments with @hide annotation and trims excessive newlines.
|
||||
"""
|
||||
result = []
|
||||
for line in text.splitlines():
|
||||
if (m := _RE_COMMENT.match(line)) is not None:
|
||||
if m[1] == "@hide":
|
||||
continue
|
||||
else:
|
||||
msg = f"Unknown annotation: {m[1]}"
|
||||
raise ValueError(msg)
|
||||
result.append(line + "\n")
|
||||
text = "".join(result)
|
||||
text = text.lstrip("\n").rstrip("\n")
|
||||
text = re.sub(r"\n{3,}", "\n\n", text)
|
||||
if text:
|
||||
text += "\n"
|
||||
return text
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,17 @@
|
||||
from pathlib import Path
|
||||
|
||||
from .base import CompileResult, Language, copy_template
|
||||
|
||||
|
||||
class LanguageBash(Language):
|
||||
NAME = "bash"
|
||||
SNIPPET_FILENAME = "bash.sh"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, tmpdir: Path, fnames: list[Path]) -> CompileResult:
|
||||
tmpdir.mkdir(parents=True)
|
||||
result = CompileResult()
|
||||
for no, fname in enumerate(fnames):
|
||||
copy_template(fname, tmpdir / f"snippet{no}.sh")
|
||||
result.run_args[fname] = ["bash", "-ex", f"{tmpdir}/snippet{no}.sh"]
|
||||
return result
|
||||
@@ -0,0 +1,125 @@
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from .base import CompileResult, Language, generic_shorten, template, trim_commonpath
|
||||
|
||||
|
||||
class LanguageCsharp(Language):
|
||||
NAME = "csharp"
|
||||
SNIPPET_FILENAME = "csharp.cs"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, tmpdir: Path, fnames: list[Path]) -> CompileResult:
|
||||
shutil.copytree("templates/csharp", tmpdir)
|
||||
|
||||
csproj_path = tmpdir / "csharp.csproj"
|
||||
csproj_path.write_text(
|
||||
csproj_path.read_text().replace(
|
||||
"../../clients/csharp/",
|
||||
f"{Path('clients/csharp').absolute()}/",
|
||||
)
|
||||
)
|
||||
|
||||
trimmed_fnames = trim_commonpath(fnames)
|
||||
|
||||
src_cases = []
|
||||
|
||||
result = CompileResult()
|
||||
|
||||
for no, (snippet_fname, trimmed_fname) in enumerate(trimmed_fnames.items()):
|
||||
content = (
|
||||
snippet_fname.read_text()
|
||||
.replace("public class Snippet", f"public class Snippet{no}")
|
||||
.replace("{collection_name}", "collection_name")
|
||||
)
|
||||
dest_path = tmpdir / "s" / trimmed_fname.parent / f"Snippet{no}.cs"
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest_path.write_text(content)
|
||||
src_cases.append(
|
||||
f'case "{snippet_fname}": await Snippet{no}.Run(); break;\n'
|
||||
)
|
||||
result.run_args[snippet_fname] = [
|
||||
"dotnet",
|
||||
f"{tmpdir}/bin/snippets-amalgamation.dll",
|
||||
snippet_fname,
|
||||
]
|
||||
|
||||
template(
|
||||
original_fname=Path("templates/csharp/Program.cs"),
|
||||
target_fname=tmpdir / "Program.cs",
|
||||
replacements={"// %cases%": "".join(src_cases)},
|
||||
)
|
||||
|
||||
subprocess.run(["dotnet", "build", "-o", "bin"], cwd=tmpdir, check=True)
|
||||
|
||||
return result
|
||||
|
||||
RE_CODE = re.compile(
|
||||
r"""
|
||||
(?P<imports> .*? )
|
||||
\s*public\ class\ Snippet\s*\{\s*
|
||||
public\ static\ async\ Task\ Run\(\)\s*\{\n
|
||||
(?P<body> .*? )
|
||||
\s*\}\s*}\s*
|
||||
$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
EXAMPLE_CODE = textwrap.dedent(
|
||||
"""\
|
||||
using Qdrant.Client;
|
||||
using Qdrant.Client.Grpc;
|
||||
|
||||
public class Snippet
|
||||
{
|
||||
public static async Task Run()
|
||||
{
|
||||
// Your code here
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
assert RE_CODE.match(EXAMPLE_CODE) is not None
|
||||
|
||||
@classmethod
|
||||
def shorten(cls, contents: str) -> str:
|
||||
if (m := LanguageCsharp.RE_CODE.match(contents)) is None:
|
||||
msg = "Invalid snippet format"
|
||||
raise ValueError(msg)
|
||||
return generic_shorten(
|
||||
m["imports"].strip() + "\n\n" + textwrap.dedent(m["body"]).strip()
|
||||
)
|
||||
|
||||
RE_RENDERED = re.compile(
|
||||
r"""
|
||||
(?P<imports> (?:using\s[^;]+;\n|\n)* )
|
||||
(?P<body> .* )
|
||||
$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unshorten(cls, contents: str) -> str:
|
||||
if m := LanguageCsharp.RE_RENDERED.match(contents):
|
||||
return textwrap.dedent(
|
||||
"""\
|
||||
{imports}
|
||||
|
||||
public class Snippet
|
||||
{{
|
||||
\tpublic static async Task Run()
|
||||
\t{{
|
||||
{body}
|
||||
\t}}
|
||||
}}
|
||||
"""
|
||||
).format(
|
||||
imports=m["imports"].strip(),
|
||||
body=textwrap.indent(m["body"].strip(), "\t" * 2),
|
||||
)
|
||||
return contents
|
||||
@@ -0,0 +1,119 @@
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from .base import (
|
||||
CompileResult,
|
||||
Language,
|
||||
copy_template,
|
||||
generic_shorten,
|
||||
template,
|
||||
trim_commonpath,
|
||||
)
|
||||
|
||||
|
||||
class LanguageGo(Language):
|
||||
NAME = "go"
|
||||
SNIPPET_FILENAME = "go.go"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, tmpdir: Path, fnames: list[Path]) -> CompileResult:
|
||||
shutil.copytree("templates/go", tmpdir)
|
||||
|
||||
trimmed_fnames = trim_commonpath(fnames)
|
||||
|
||||
src_imports = []
|
||||
src_cases = []
|
||||
|
||||
result = CompileResult()
|
||||
|
||||
for no, (snippet_fname, trimmed_fname) in enumerate(trimmed_fnames.items()):
|
||||
dest_path = tmpdir / "s" / trimmed_fname
|
||||
copy_template(snippet_fname, dest_path)
|
||||
src_imports.append(
|
||||
f's{no} "example.com/snippets-amalgamation/{("s" / trimmed_fname).parent}"'
|
||||
)
|
||||
src_cases.append(f'case "{snippet_fname}": s{no}.Main()\n')
|
||||
result.run_args[snippet_fname] = [tmpdir / "tester", snippet_fname]
|
||||
|
||||
template(
|
||||
original_fname=Path("templates/go/main.go"),
|
||||
target_fname=tmpdir / "main.go",
|
||||
replacements={
|
||||
"// %imports%": "\n".join(src_imports),
|
||||
"// %cases%": "\n".join(src_cases),
|
||||
},
|
||||
)
|
||||
|
||||
subprocess.run(["go", "build", "-o", "tester", "."], cwd=tmpdir, check=True)
|
||||
|
||||
return result
|
||||
|
||||
RE_CODE = re.compile(
|
||||
r"""
|
||||
package\ [a-zA-Z0-9_]+\n
|
||||
(?P<imports> .*? )
|
||||
func\ Main\(\)\ \{\n
|
||||
(?P<body> .*? )
|
||||
\}\s*$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
EXAMPLE_CODE = textwrap.dedent(
|
||||
"""\
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/qdrant/go-client/qdrant"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
// Your code here
|
||||
}
|
||||
"""
|
||||
)
|
||||
assert RE_CODE.match(EXAMPLE_CODE) is not None
|
||||
|
||||
@classmethod
|
||||
def shorten(cls, contents: str) -> str:
|
||||
if (m := LanguageGo.RE_CODE.match(contents)) is None:
|
||||
msg = "Invalid snippet format"
|
||||
raise ValueError(msg)
|
||||
return generic_shorten(
|
||||
m["imports"].strip() + "\n\n" + textwrap.dedent(m["body"]).strip()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def format(cls, fnames: list[str]) -> None:
|
||||
subprocess.run(["gofmt", "-w", *fnames], check=True)
|
||||
|
||||
RE_RENDERED = re.compile(
|
||||
r"""
|
||||
(?P<imports> (?:import\s*\([^)]+\)\n|\n)* )
|
||||
(?P<body> .* )
|
||||
$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unshorten(cls, contents: str) -> str:
|
||||
if m := LanguageGo.RE_RENDERED.match(contents):
|
||||
return textwrap.dedent(
|
||||
"""\
|
||||
package snippet
|
||||
|
||||
{imports}
|
||||
|
||||
func Main() {{
|
||||
{body}
|
||||
}}
|
||||
"""
|
||||
).format(
|
||||
imports=m["imports"].strip(),
|
||||
body=textwrap.indent(m["body"].strip(), "\t"),
|
||||
)
|
||||
return contents
|
||||
@@ -0,0 +1,128 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from .base import CompileResult, Language, generic_shorten, template, trim_commonpath
|
||||
|
||||
|
||||
class LanguageJava(Language):
|
||||
NAME = "java"
|
||||
SNIPPET_FILENAME = "java.java"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, tmpdir: Path, fnames: list[Path]) -> CompileResult:
|
||||
(tmpdir / "src/main/java/s").mkdir(parents=True)
|
||||
shutil.copyfile("templates/java/build.gradle", f"{tmpdir}/build.gradle")
|
||||
shutil.copyfile("templates/java/settings.gradle", f"{tmpdir}/settings.gradle")
|
||||
|
||||
trimmed_fnames = trim_commonpath(fnames)
|
||||
|
||||
result = CompileResult()
|
||||
src_cases = []
|
||||
|
||||
for no, (snippet_fname, trimmed_fname) in enumerate(trimmed_fnames.items()):
|
||||
content = (
|
||||
snippet_fname.read_text()
|
||||
.replace("public class Snippet", f"public class Snippet{no}")
|
||||
.replace("{collection_name}", "collection_name")
|
||||
)
|
||||
dest_path = (
|
||||
tmpdir / "src/main/java/s" / trimmed_fname.parent / f"Snippet{no}.java"
|
||||
)
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest_path.write_text(content)
|
||||
src_cases.append(f'case "{snippet_fname}": Snippet{no}.run(); break;\n')
|
||||
result.run_args[snippet_fname] = [
|
||||
"java",
|
||||
"-jar",
|
||||
f"{tmpdir}/build/libs/app-1.0.0-all.jar",
|
||||
snippet_fname,
|
||||
]
|
||||
|
||||
template(
|
||||
original_fname=Path("templates/java/src/main/java/Main.java"),
|
||||
target_fname=tmpdir / "src/main/java/Main.java",
|
||||
replacements={
|
||||
"// %cases%": "\n".join(src_cases),
|
||||
},
|
||||
)
|
||||
|
||||
gradlew = os.path.abspath("templates/java/gradlew")
|
||||
client = os.path.abspath("clients/java")
|
||||
subprocess.run(
|
||||
[gradlew, "--no-daemon", f"-PclientPath={client}", "shadowJar"],
|
||||
cwd=tmpdir,
|
||||
check=True,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
RE_CODE = re.compile(
|
||||
r"""
|
||||
package\s[a-zA-Z0-9_.]+;\n
|
||||
(?P<imports> .*? )
|
||||
\s*public\ class\ Snippet\ \{\n
|
||||
\s*public\ static\ void\ run\(\)\ throws\ Exception\ \{\n
|
||||
(?P<body> .*? )
|
||||
\s*\}\s*}\s*
|
||||
$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
EXAMPLE_CODE = textwrap.dedent(
|
||||
"""\
|
||||
package com.example.snippets_amalgamation;
|
||||
|
||||
import io.qdrant.client.QdrantClient;
|
||||
|
||||
public class Snippet {
|
||||
public static void run() throws Exception {
|
||||
// Your code here
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
assert RE_CODE.match(EXAMPLE_CODE) is not None
|
||||
|
||||
@classmethod
|
||||
def shorten(cls, contents: str) -> str:
|
||||
if (m := LanguageJava.RE_CODE.match(contents)) is None:
|
||||
msg = "Invalid snippet format"
|
||||
raise ValueError(msg)
|
||||
return generic_shorten(
|
||||
m["imports"].strip() + "\n\n" + textwrap.dedent(m["body"]).strip()
|
||||
)
|
||||
|
||||
RE_RENDERED = re.compile(
|
||||
r"""
|
||||
(?P<imports> (?: import\s[^;]+;\n|\n)* )
|
||||
(?P<body> .* )
|
||||
$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unshorten(cls, contents: str) -> str:
|
||||
if m := LanguageJava.RE_RENDERED.match(contents):
|
||||
return textwrap.dedent(
|
||||
"""\
|
||||
package com.example.snippets_amalgamation;
|
||||
|
||||
{imports}
|
||||
|
||||
public class Snippet {{
|
||||
public static void run() throws Exception {{
|
||||
{body}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
).format(
|
||||
imports=m["imports"].strip(),
|
||||
body=textwrap.indent(m["body"].strip(), " " * 16),
|
||||
)
|
||||
return contents
|
||||
@@ -0,0 +1,34 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from .base import CompileResult, Language, copy_template, trim_commonpath
|
||||
|
||||
|
||||
class LanguagePython(Language):
|
||||
NAME = "python"
|
||||
SNIPPET_FILENAME = "python.py"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, tmpdir: Path, fnames: list[Path]) -> CompileResult:
|
||||
trimmed_fnames = trim_commonpath(fnames)
|
||||
result = CompileResult()
|
||||
|
||||
for snippet_fname, trimmed_fname in trimmed_fnames.items():
|
||||
target_path = tmpdir / str(trimmed_fname).replace("-", "_")
|
||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
dir = tmpdir
|
||||
for component in Path(str(trimmed_fname).replace("-", "_")).parts:
|
||||
(dir / "__init__.py").touch()
|
||||
dir /= component
|
||||
copy_template(snippet_fname, target_path)
|
||||
result.run_args[snippet_fname] = [
|
||||
"uv",
|
||||
"--project=templates/python",
|
||||
"run",
|
||||
target_path,
|
||||
]
|
||||
|
||||
p = subprocess.run(["uv", "--project=templates/python", "run", "mypy", tmpdir])
|
||||
result.has_issues = p.returncode != 0
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,128 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from .base import (
|
||||
CompileResult,
|
||||
Language,
|
||||
copy_template,
|
||||
generic_shorten,
|
||||
template,
|
||||
trim_commonpath,
|
||||
)
|
||||
|
||||
|
||||
class LanguageRust(Language):
|
||||
NAME = "rust"
|
||||
SNIPPET_FILENAME = "rust.rs"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, tmpdir: Path, fnames: list[Path]) -> CompileResult:
|
||||
tmpdir.mkdir()
|
||||
(tmpdir / "src").mkdir()
|
||||
shutil.copyfile("templates/rust/Cargo.toml", f"{tmpdir}/Cargo.toml")
|
||||
shutil.copyfile("templates/rust/Cargo.lock", f"{tmpdir}/Cargo.lock")
|
||||
|
||||
src_mods = []
|
||||
src_cases = []
|
||||
|
||||
trimmed_fnames = trim_commonpath(fnames)
|
||||
|
||||
result = CompileResult()
|
||||
binary_path = Path.cwd() / "templates/rust/target/debug/snippets-amalgamation"
|
||||
|
||||
for no, (snippet_fname, trimmed_fname) in enumerate(trimmed_fnames.items()):
|
||||
copy_template(snippet_fname, tmpdir / "src" / "s" / trimmed_fname)
|
||||
src_mods.append(f'#[path = "s/{trimmed_fname}"] mod s{no};')
|
||||
src_cases.append(f'"{snippet_fname}" => s{no}::main().await.unwrap(),\n')
|
||||
result.run_args[snippet_fname] = [binary_path, snippet_fname]
|
||||
|
||||
template(
|
||||
original_fname=Path("templates/rust/src/main.rs"),
|
||||
target_fname=tmpdir / "src/main.rs",
|
||||
replacements={
|
||||
"// %mods%": "\n".join(src_mods),
|
||||
"// %cases%": "\n".join(src_cases),
|
||||
},
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
["cargo", "build"],
|
||||
cwd=tmpdir,
|
||||
env={
|
||||
**os.environ,
|
||||
"CARGO_TARGET_DIR": str(Path.cwd() / "templates/rust/target"),
|
||||
},
|
||||
check=True,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def format(cls, fnames: list[str]) -> None:
|
||||
subprocess.run(["rustfmt", "--edition=2024", *fnames], check=True)
|
||||
|
||||
RE_CODE = re.compile(
|
||||
r"""
|
||||
(?P<uses> (?:use\s[^;]+;\n|\n)* )
|
||||
pub\s+async\s+fn\s+main\(\)\s*->\s*anyhow::Result<\(\)>\s*\{\n
|
||||
(?P<body> .*? )
|
||||
\s*Ok\(\(\)\)\n
|
||||
\}\n?
|
||||
$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
EXAMPLE_CODE = textwrap.dedent(
|
||||
"""\
|
||||
use qdrant_client::Qdrant;
|
||||
|
||||
pub async fn main() -> anyhow::Result<()> {
|
||||
// Your code here
|
||||
|
||||
Ok(())
|
||||
}
|
||||
"""
|
||||
)
|
||||
assert RE_CODE.match(EXAMPLE_CODE) is not None
|
||||
|
||||
@classmethod
|
||||
def shorten(cls, contents: str) -> str:
|
||||
if (m := LanguageRust.RE_CODE.match(contents)) is None:
|
||||
msg = "Invalid snippet format"
|
||||
raise ValueError(msg)
|
||||
return generic_shorten(
|
||||
m["uses"].strip() + "\n\n" + textwrap.dedent(m["body"]).strip()
|
||||
)
|
||||
|
||||
RE_RENDERED = re.compile(
|
||||
r"""
|
||||
(?P<uses> (?:use\s[^;]+;\n|\n)* )
|
||||
(?P<body> .* )
|
||||
$
|
||||
""",
|
||||
re.DOTALL | re.VERBOSE,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unshorten(cls, contents: str) -> str:
|
||||
if m := LanguageRust.RE_RENDERED.match(contents):
|
||||
return textwrap.dedent(
|
||||
"""\
|
||||
{uses}
|
||||
|
||||
pub async fn main() -> anyhow::Result<()> {{
|
||||
{body}
|
||||
|
||||
Ok(())
|
||||
}}
|
||||
"""
|
||||
).format(
|
||||
uses=m["uses"].strip(),
|
||||
body=textwrap.indent(m["body"].strip(), " " * 4),
|
||||
)
|
||||
return contents
|
||||
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from .base import CompileResult, Language, copy_template, trim_commonpath
|
||||
|
||||
|
||||
class LanguageTypescript(Language):
|
||||
NAME = "typescript"
|
||||
SNIPPET_FILENAME = "typescript.ts"
|
||||
|
||||
@classmethod
|
||||
def compile(cls, tmpdir: Path, fnames: list[Path]) -> CompileResult:
|
||||
tmpdir.mkdir()
|
||||
|
||||
package_data = json.loads(Path("templates/typescript/package.json").read_text())
|
||||
|
||||
package_data["dependencies"]["@qdrant/js-client-rest"] = "file:" + str(
|
||||
Path("clients/typescript/packages/js-client-rest").resolve().absolute()
|
||||
)
|
||||
(tmpdir / "package.json").write_text(json.dumps(package_data, indent=2))
|
||||
|
||||
shutil.copyfile(
|
||||
"templates/typescript/package-lock.json", f"{tmpdir}/package-lock.json"
|
||||
)
|
||||
|
||||
trimmed_fnames = trim_commonpath(fnames)
|
||||
result = CompileResult()
|
||||
|
||||
for snippet_fname, trimmed_fname in trimmed_fnames.items():
|
||||
dest_path = tmpdir / "s" / trimmed_fname
|
||||
copy_template(snippet_fname, dest_path)
|
||||
result.run_args[snippet_fname] = ["node", dest_path.with_suffix(".js")]
|
||||
|
||||
p = subprocess.run(["npm", "install"], cwd=tmpdir)
|
||||
if p.returncode != 0:
|
||||
result.has_issues = True
|
||||
return result
|
||||
|
||||
p = subprocess.run(
|
||||
["node_modules/.bin/tsc"]
|
||||
+ ["s" / trimmed_fname for trimmed_fname in trimmed_fnames.values()],
|
||||
cwd=tmpdir,
|
||||
)
|
||||
result.has_issues = p.returncode != 0
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def format(cls, fnames: list[str]) -> None:
|
||||
# subprocess.run(["npx", "prettier", "--write", *fnames], check=True)
|
||||
pass
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Migrate the specified `.md` snippet to the runnable snippet file.
|
||||
|
||||
E.g., converts
|
||||
`snippets/create-collection/simple/python.md`
|
||||
to
|
||||
`snippets/create-collection/simple/python.py`
|
||||
|
||||
Not reliable, might require manual adjustments.
|
||||
Intended to be manually used to migrate non-runnable snippets into runnable
|
||||
ones.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from lib import log
|
||||
from lib.languages import ALL_LANGUAGES
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("MD_FILE", nargs="+", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
for md_file in args.MD_FILE:
|
||||
if md_file.suffix != ".md":
|
||||
log(f"Skipping non-markdown file: {md_file}")
|
||||
continue
|
||||
for lang in ALL_LANGUAGES:
|
||||
if md_file.name == f"{lang.NAME}.md":
|
||||
break
|
||||
else:
|
||||
log(f"Skipping unknown language file: {md_file}")
|
||||
continue
|
||||
|
||||
lines = md_file.read_text().rstrip().splitlines()
|
||||
if not lines[0].startswith("```") or lines[-1] != "```":
|
||||
continue
|
||||
content = "\n".join(lines[1:-1]) + "\n"
|
||||
content = lang.unshorten(content)
|
||||
|
||||
log(f"Converting {md_file} to {lang.SNIPPET_FILENAME}")
|
||||
(md_file.parent / lang.SNIPPET_FILENAME).write_text(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "snippet-checker"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"requests>=2.32.5",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"mypy>=1.18.2",
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
with import <nixpkgs> { };
|
||||
mkShell {
|
||||
buildInputs = [
|
||||
# for ./sync-clients.py
|
||||
git
|
||||
|
||||
# for csharp client
|
||||
dotnetCorePackages.sdk_8_0-bin
|
||||
|
||||
# for go client
|
||||
go
|
||||
|
||||
# for java client
|
||||
openjdk
|
||||
|
||||
# for python client
|
||||
uv
|
||||
|
||||
# for rust client
|
||||
cargo
|
||||
rustfmt
|
||||
|
||||
# for typescript client
|
||||
pnpm
|
||||
];
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../qdrant-landing/content/documentation/headless/snippets
|
||||
Executable
+202
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
|
||||
# Change these to use different revisions by default/on CI
|
||||
CSHARP_CLIENT_REVISION = "main"
|
||||
JAVA_CLIENT_REVISION = "master"
|
||||
TYPESCRIPT_CLIENT_REVISION = "master"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Fetch and prepare Qdrant client libraries."
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
fetch_parser = subparsers.add_parser(
|
||||
"fetch",
|
||||
help="""Fetch client libraries and put them in the clients/ directory.
|
||||
This is for java and C# clients.""",
|
||||
)
|
||||
fetch_parser.add_argument(
|
||||
"--csharp",
|
||||
help="Fetch and build the C# client.",
|
||||
nargs="?",
|
||||
const=CSHARP_CLIENT_REVISION,
|
||||
metavar="GIT_REV",
|
||||
)
|
||||
fetch_parser.add_argument(
|
||||
"--java",
|
||||
help="Fetch the Java client.",
|
||||
nargs="?",
|
||||
const=JAVA_CLIENT_REVISION,
|
||||
metavar="GIT_REV",
|
||||
)
|
||||
fetch_parser.add_argument(
|
||||
"--typescript",
|
||||
help="Fetch the TypeScript client.",
|
||||
nargs="?",
|
||||
const=TYPESCRIPT_CLIENT_REVISION,
|
||||
metavar="GIT_REV",
|
||||
)
|
||||
|
||||
lock_parser = subparsers.add_parser(
|
||||
"lock",
|
||||
help="Update lockfiles for templates/ directories.",
|
||||
)
|
||||
lock_parser.add_argument(
|
||||
"--go",
|
||||
help="Set Go client version",
|
||||
nargs="?",
|
||||
const="master",
|
||||
metavar="GIT_REV",
|
||||
)
|
||||
lock_parser.add_argument(
|
||||
"--rust",
|
||||
help="""Set Rust client version.
|
||||
Format: branch=BRANCH_NAME or tag=TAG_NAME or rev=COMMIT_HASH""",
|
||||
nargs="?",
|
||||
const="branch=master",
|
||||
metavar="KIND=REV",
|
||||
)
|
||||
lock_parser.add_argument(
|
||||
"--python",
|
||||
help="""Set Python client version.
|
||||
Format: branch=BRANCH_NAME or tag=TAG_NAME or rev=COMMIT_HASH""",
|
||||
nargs="?",
|
||||
const="branch=master",
|
||||
metavar="KIND=REV",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
ok = False
|
||||
if args.command == "fetch":
|
||||
if args.csharp is not None:
|
||||
url = "https://github.com/qdrant/qdrant-dotnet"
|
||||
dir = BASE_DIR / "clients" / "csharp"
|
||||
checkout_repo(url, dir, args.csharp)
|
||||
prepare_csharp(dir)
|
||||
ok = True
|
||||
if args.java is not None:
|
||||
url = "https://github.com/qdrant/java-client"
|
||||
dir = BASE_DIR / "clients" / "java"
|
||||
checkout_repo(url, dir, "master")
|
||||
ok = True
|
||||
if args.typescript is not None:
|
||||
url = "https://github.com/qdrant/qdrant-js"
|
||||
dir = BASE_DIR / "clients" / "typescript"
|
||||
checkout_repo(url, dir, args.typescript)
|
||||
prepare_typescript(dir)
|
||||
ok = True
|
||||
if not ok:
|
||||
print("Specify at least one of --csharp, --java, --typescript to fetch.")
|
||||
elif args.command == "lock":
|
||||
if args.go is not None:
|
||||
lock_go(args.go)
|
||||
ok = True
|
||||
if args.rust is not None:
|
||||
lock_rust(args.rust)
|
||||
ok = True
|
||||
if args.python is not None:
|
||||
lock_python(args.python)
|
||||
ok = True
|
||||
if not ok:
|
||||
print("Specify at least one of --go, --rust to lock.")
|
||||
|
||||
|
||||
def prepare_csharp(dir: Path) -> None:
|
||||
subprocess.run(["git", "-C", dir, "clean", "-fxd"], check=True)
|
||||
subprocess.run(
|
||||
["dotnet", "run", "--project", "build", "--", "download-protos", "restore"],
|
||||
check=True,
|
||||
cwd=dir,
|
||||
)
|
||||
subprocess.run(
|
||||
["dotnet", "build", "-c", "Release", "--nologo"],
|
||||
check=True,
|
||||
cwd=dir,
|
||||
)
|
||||
|
||||
|
||||
def prepare_typescript(dir: Path) -> None:
|
||||
subprocess.run(["pnpm", "install"], cwd=dir, check=True)
|
||||
subprocess.run(["pnpm", "-r", "build"], cwd=dir, check=True)
|
||||
|
||||
|
||||
def lock_go(rev: str) -> None:
|
||||
dir = BASE_DIR / "templates" / "go"
|
||||
subprocess.run(
|
||||
["go", "get", f"github.com/qdrant/go-client@{rev}"], cwd=dir, check=True
|
||||
)
|
||||
subprocess.run(["go", "mod", "tidy"], cwd=dir, check=True)
|
||||
|
||||
|
||||
def lock_rust(spec: str) -> None:
|
||||
dir = BASE_DIR / "templates" / "rust"
|
||||
update_toml_spec(dir / "Cargo.toml", spec)
|
||||
subprocess.run(["cargo", "update", "-p", "qdrant-client"], cwd=dir, check=True)
|
||||
|
||||
|
||||
def lock_python(spec: str) -> None:
|
||||
dir = BASE_DIR / "templates" / "python"
|
||||
update_toml_spec(dir / "pyproject.toml", spec)
|
||||
subprocess.run(["uv", "lock"], cwd=dir, check=True)
|
||||
|
||||
|
||||
def update_toml_spec(path: Path, spec: str) -> None:
|
||||
try:
|
||||
kind, rev = spec.split("=", 1)
|
||||
if kind not in ("branch", "tag", "rev"):
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
print("Invalid format for --rust/--python.")
|
||||
print("Use branch=BRANCH_NAME or tag=TAG_NAME or rev=COMMIT_HASH.")
|
||||
sys.exit(1)
|
||||
|
||||
toml_content = path.read_text().splitlines()
|
||||
re_line = re.compile(r'^(qdrant-client = \{ git = "[^"]+").*$')
|
||||
idx, m = next(
|
||||
(i, m)
|
||||
for i, line in enumerate(toml_content)
|
||||
if (m := re_line.match(line)) is not None
|
||||
)
|
||||
|
||||
toml_content[idx] = f'{m[1]}, {kind} = "{rev}" }}'
|
||||
path.write_text("\n".join(toml_content) + "\n")
|
||||
|
||||
|
||||
def checkout_repo(
|
||||
repo_url: str,
|
||||
dest_dir: Path,
|
||||
rev: str,
|
||||
) -> None:
|
||||
if not dest_dir.exists():
|
||||
print(f"Cloning repository from {repo_url} to {dest_dir}...")
|
||||
subprocess.run(["git", "clone", repo_url, dest_dir], check=True)
|
||||
else:
|
||||
print(f"Repository already exists at {dest_dir}. Fetching latest changes...")
|
||||
subprocess.run(["git", "-C", dest_dir, "fetch", "-p", "origin"], check=True)
|
||||
|
||||
# Try remote branch first (ignore local branches)
|
||||
# If this check fails, it's either a commit hash, local branch, or tag
|
||||
p = subprocess.run(
|
||||
["git", "-C", dest_dir, "rev-parse", "--verify", f"refs/remotes/origin/{rev}"],
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if p.returncode == 0:
|
||||
rev = p.stdout.strip()
|
||||
|
||||
subprocess.run(["git", "-C", dest_dir, "checkout", "--detach", rev], check=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,2 @@
|
||||
/bin
|
||||
/obj
|
||||
@@ -0,0 +1,22 @@
|
||||
try
|
||||
{
|
||||
foreach (var arg in args)
|
||||
{
|
||||
switch (arg)
|
||||
{
|
||||
// %cases%
|
||||
default:
|
||||
Console.WriteLine($"Unknown argument: {arg}");
|
||||
Environment.Exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine($"Error occurred: {e.Message}");
|
||||
Console.Error.WriteLine(e.StackTrace);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>snippets-amalgamation</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../clients/csharp/src/Qdrant.Client/Qdrant.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
module example.com/snippets-amalgamation
|
||||
|
||||
go 1.25.2
|
||||
|
||||
require github.com/qdrant/go-client v1.16.0
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/qdrant/go-client v1.16.0 h1:DTkC3eppWXKhjQs+IgA9LFKOkSjJ4sTHs2jJPCni7dY=
|
||||
github.com/qdrant/go-client v1.16.0/go.mod h1:I+EL3h4HRoRTeHtbfOd/4kDXwCukZfkd41j/9wryGkw=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "github.com/qdrant/go-client/qdrant"
|
||||
"os"
|
||||
// %imports%
|
||||
)
|
||||
|
||||
func main() {
|
||||
for _, arg := range os.Args[1:] {
|
||||
switch arg {
|
||||
// %cases%
|
||||
default:
|
||||
fmt.Printf("Unknown argument: %s\n", arg)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,51 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
}
|
||||
|
||||
group = 'com.example.snippets_amalgamation'
|
||||
version = '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.qdrant:client'
|
||||
|
||||
// gRPC dependencies - use the same version as Qdrant client
|
||||
implementation 'io.grpc:grpc-netty-shaded:1.65.1'
|
||||
implementation 'io.grpc:grpc-protobuf:1.65.1'
|
||||
implementation 'io.grpc:grpc-stub:1.65.1'
|
||||
implementation 'io.grpc:grpc-services:1.65.1'
|
||||
|
||||
// Protobuf runtime
|
||||
implementation 'com.google.protobuf:protobuf-java:3.25.4'
|
||||
|
||||
// Logging
|
||||
implementation 'org.slf4j:slf4j-simple:2.0.14'
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'com.example.snippets_amalgamation.Main'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
// Create a proper fat jar and merge service descriptor files (required by gRPC)
|
||||
shadowJar {
|
||||
mergeServiceFiles()
|
||||
archiveBaseName.set('app')
|
||||
archiveClassifier.set('all')
|
||||
}
|
||||
|
||||
// Keep the plain jar minimal (no dependency unpacking)
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'com.example.snippets_amalgamation.Main'
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1 @@
|
||||
includeBuild(clientPath)
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.snippets_amalgamation;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
for (String arg : args) {
|
||||
switch (arg) {
|
||||
// %cases%
|
||||
default:
|
||||
System.out.println("Unknown argument: " + arg);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error occurred: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
print(f"Hello, {QdrantClient}!")
|
||||
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "snippets-amalgamation"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"qdrant-client",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
qdrant-client = { git = "https://github.com/qdrant/qdrant-client", tag = "v1.16.0" }
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"mypy>=1.18.2",
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
/target
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "snippets-amalgamation"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
qdrant-client = { git = "https://github.com/qdrant/rust-client", branch = "master" }
|
||||
tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros"] }
|
||||
@@ -0,0 +1,14 @@
|
||||
// %mods%
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
for arg in std::env::args().skip(1) {
|
||||
match arg.as_str() {
|
||||
// %cases%
|
||||
_ => {
|
||||
eprintln!("Unknown argument: {}", arg);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/node_modules
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "snippets-amalgamation",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "1.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@qdrant/js-client-rest": "file:../../clients/typescript/packages/js-client-rest"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user