Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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: 2 additions & 0 deletions docs/source/_toctree.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@
title: kernels skills
- local: cli-create-and-upload-card
title: kernels create-and-upload-card
- local: cli-collate-readme
title: kernels collate-readme
title: CLI Reference
17 changes: 17 additions & 0 deletions docs/source/cli-collate-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### kernels collate-readme

Use `kernels collate-readme <repo_id>` to generate a README for the `main`
Copy link
Member

Choose a reason for hiding this comment

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

Repo READMEs are instantiated cards. Shouldn't the versions be enumerated by card filling?

Copy link
Member Author

@sayakpaul sayakpaul Mar 11, 2026

Choose a reason for hiding this comment

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

Sorry didn't get it. Did you mean #287 should have a section on "Versions" in the card template and populate it automatically?

Even if that's the case, I think the PR could still have its own scope because we're tackling the README that will be included in the main revision of the kernel repo. The builder will automatically generate the cards and push to particular revisions in which case the README will still likely stay blank. Or am I missing something?

Copy link
Member

@danieldk danieldk Mar 11, 2026

Choose a reason for hiding this comment

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

For now I think main will be the default branch that is shown? Then main's README should be the instantiated card of the most recent version. I am not sure if all the version branches need individual cards, since we do not want people to actually use older versions. We leave them around to avoid breakage for people who haven't updated their code to use the latest kernel version yet, other than that, we should hide the old versions as much as possible.

Copy link
Member

Choose a reason for hiding this comment

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

i think it makes sense ^

branch of a kernel repository. This README lists all available version
branches (e.g., `v1`, `v2`) with links, so visitors landing on
the default branch can discover the kernel's versions.

By default, the README is printed to stdout. Use `--push-to-hub` to upload
it directly to the `main` branch on the Hub.

```bash
# Print
kernels collate-readme kernels-community/activation

# Push to the Hub
kernels collate-readme kernels-community/activation --push-to-hub
```
24 changes: 24 additions & 0 deletions kernels/src/kernels/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
from pathlib import Path

from kernels.cli.collate_readme import collate_readme_from_versions
from kernels.cli.doc import generate_readme_for_kernel
from kernels.cli.init import parse_kernel_name, run_init
from kernels.cli.skills import add_skill
Expand Down Expand Up @@ -278,6 +279,22 @@ def main():
)
repocard_parser.set_defaults(func=create_and_upload_card)

collate_readme_parser = subparsers.add_parser(
"collate-readme",
help="Generate a main-branch README listing available kernel versions.",
)
collate_readme_parser.add_argument(
"repo_id",
type=str,
help="The kernel repo ID (e.g., kernels-community/activation)",
)
collate_readme_parser.add_argument(
"--push-to-hub",
action="store_true",
help="Push the generated README to the main branch on the Hub.",
)
collate_readme_parser.set_defaults(func=run_collate_readme)

args = parser.parse_args()
args.func(args)

Expand Down Expand Up @@ -348,6 +365,13 @@ def upload_kernels(args):
)


def run_collate_readme(args):
collate_readme_from_versions(
repo_id=args.repo_id,
push_to_hub=args.push_to_hub,
)


def create_and_upload_card(args):
if not args.repo_id and args.create_pr:
raise ValueError("`create_pr` cannot be True when `repo_id` is None.")
Expand Down
70 changes: 70 additions & 0 deletions kernels/src/kernels/cli/collate_readme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from kernels._versions import _get_available_versions
from kernels.utils import _get_hf_api


def generate_main_readme(repo_id: str) -> str:
versions = _get_available_versions(repo_id)

if not versions:
raise ValueError(
f"No versions found for `{repo_id}`. "
"Upload at least one versioned kernel before generating a main README."
)

kernel_name = repo_id.split("/")[-1]
hub_url = f"https://huggingface.co/{repo_id}"

lines = [
"---",
"tags:",
"- kernels",
"library_name: kernels",
"---",
"",
f"# {kernel_name}",
"",
"This kernel is available in the following versions. "
"Please refer to the version branches for details.",
"",
"## Available versions",
"",
"| Version | Branch |",
"| ------- | ------ |",
]

for version_num in sorted(versions.keys()):
branch_name = f"v{version_num}"
branch_url = f"{hub_url}/tree/{branch_name}"
lines.append(f"| {version_num} | [{branch_name}]({branch_url}) |")

lines.append("")
lines.append("## Quick start")
lines.append("")
lines.append("```python")
lines.append("from kernels import get_kernel")
lines.append("")
lines.append(f'kernel = get_kernel("{repo_id}", version=<version>)')
lines.append("```")
lines.append("")

return "\n".join(lines)


def collate_readme_from_versions(
repo_id: str,
push_to_hub: bool = False,
):
readme_content = generate_main_readme(repo_id)

if push_to_hub:
api = _get_hf_api()
api.upload_file(
path_or_fileobj=readme_content.encode("utf-8"),
path_in_repo="README.md",
repo_id=repo_id,
revision="main",
commit_message="Update main README with available versions.",
)
print(f"README pushed to https://huggingface.co/{repo_id}")
else:
print(readme_content)
110 changes: 110 additions & 0 deletions kernels/tests/test_collate_readme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from dataclasses import dataclass
from unittest.mock import MagicMock, patch

import pytest

from kernels.cli.collate_readme import (
collate_readme_from_versions,
generate_main_readme,
)


def _make_versions(version_nums: list[int]) -> dict:
versions = {}
for v in version_nums:
ref = MagicMock()
ref.name = f"v{v}"
ref.ref = f"refs/heads/v{v}"
versions[v] = ref
return versions


@dataclass
class CollateArgs:
repo_id: str
push_to_hub: bool = False


PATCH_VERSIONS = "kernels.cli.collate_readme._get_available_versions"
PATCH_API = "kernels.cli.collate_readme._get_hf_api"


class TestGenerateMainReadme:
@patch(PATCH_VERSIONS)
def test_basic_output(self, mock_versions):
mock_versions.return_value = _make_versions([1, 2])
readme = generate_main_readme("kernels-community/my-kernel")

assert "# my-kernel" in readme
assert "| 1 | [v1]" in readme
assert "| 2 | [v2]" in readme
assert "kernels-community/my-kernel" in readme

@patch(PATCH_VERSIONS)
def test_has_frontmatter(self, mock_versions):
mock_versions.return_value = _make_versions([1])
readme = generate_main_readme("org/kernel")

assert readme.startswith("---\n")
assert "tags:" in readme
assert "- kernels" in readme
assert "library_name: kernels" in readme

@patch(PATCH_VERSIONS)
def test_versions_sorted(self, mock_versions):
mock_versions.return_value = _make_versions([3, 1, 2])
readme = generate_main_readme("org/kernel")

v1_pos = readme.index("| 1 |")
v2_pos = readme.index("| 2 |")
v3_pos = readme.index("| 3 |")
assert v1_pos < v2_pos < v3_pos

@patch(PATCH_VERSIONS)
def test_hub_urls(self, mock_versions):
mock_versions.return_value = _make_versions([1])
readme = generate_main_readme("kernels-community/activation")

assert "https://huggingface.co/kernels-community/activation/tree/v1" in readme

@patch(PATCH_VERSIONS)
def test_quick_start_section(self, mock_versions):
mock_versions.return_value = _make_versions([1])
readme = generate_main_readme("org/kernel")

assert "## Quick start" in readme
assert 'get_kernel("org/kernel"' in readme

@patch(PATCH_VERSIONS)
def test_no_versions_raises(self, mock_versions):
mock_versions.return_value = {}

with pytest.raises(ValueError, match="No versions found"):
generate_main_readme("org/kernel")


class TestCollateReadmeCLI:
@patch(PATCH_VERSIONS)
def test_prints_to_stdout(self, mock_versions, capsys):
mock_versions.return_value = _make_versions([1, 2])

collate_readme_from_versions(repo_id="org/kernel")

captured = capsys.readouterr()
assert "# kernel" in captured.out
assert "| 1 |" in captured.out

@patch(PATCH_API)
@patch(PATCH_VERSIONS)
def test_push_to_hub(self, mock_versions, mock_api):
mock_versions.return_value = _make_versions([1])
api_instance = MagicMock()
mock_api.return_value = api_instance

collate_readme_from_versions(repo_id="org/kernel", push_to_hub=True)

api_instance.upload_file.assert_called_once()
call_kwargs = api_instance.upload_file.call_args[1]
assert call_kwargs["path_in_repo"] == "README.md"
assert call_kwargs["repo_id"] == "org/kernel"
assert call_kwargs["revision"] == "main"
Loading