Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve handling of the labels #438

Merged
merged 2 commits into from
Oct 24, 2024
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
54 changes: 18 additions & 36 deletions _extensions/gallery.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import os
import yaml
import glob
from pathlib import Path
import sphinx.util
import re

from collections import Counter

from dodo import CATNAME_TO_CAT_MAP, CAT_TO_CATNAME_MAP

logger = sphinx.util.logging.getLogger('category-gallery-extension')

DEFAULT_GALLERY_CONF = {
'default_extensions': ['*.ipynb'],
'examples_dir': os.path.join('..', 'examples'),
'labels_dir': 'labels',
'github_project': None,
'intro': 'Sample intro',
'title': 'A sample gallery title',
Expand Down Expand Up @@ -75,29 +75,10 @@ def clean_category_name(category_name):
# remove any emoji's and or leading whitespace
return re.sub(r'[^a-zA-Z0-9\s]', '', category_name).strip().lower().replace(" ", "_")

def get_labels_path(app):
doc_dir = app.builder.srcdir
gallery_conf = app.config.gallery_conf
if not '_static' in app.config.html_static_path:
raise FileNotFoundError(
'Gallery expects `html_static_path` to contain a "doc/_static/" '
'folder, in which the labels will be looked up.'
)
static_dir = os.path.join(doc_dir, '_static')
labels_dir = gallery_conf['labels_dir']
return os.path.join(static_dir, labels_dir)

def generate_labels_rst(labels_path, labels):
labels_str = ''
def generate_labels_rst(labels):
labels_str = ' .. container:: hv-gallery-badges \n\n'
for label in labels:
label_svg = os.path.join(labels_path, f'{label}.svg')
if not os.path.exists(label_svg):
raise FileNotFoundError(
f'Label {label!r} must have an SVG file in {labels_path}'
)
# Prepend / to make it an "absolute" path from the root folder.
label_svg = '/' + label_svg
labels_str += ' ' * 8 + f'.. image:: {label_svg}\n'
labels_str += ' ' * 11 + f':bdg-primary-line:`{label}`\n'
return labels_str

def generate_last_updated_rst(last_updated):
Expand All @@ -109,7 +90,7 @@ def generate_last_updated_rst(last_updated):
"""
return ''

def generate_card_grid(app, rst, projects, labels_path):
def generate_card_grid(app, rst, projects):
rst += '\n.. grid:: 2 2 4 4\n :gutter: 3\n :margin: 0\n'
toctree_entries=[]
for section in projects:
Expand Down Expand Up @@ -141,7 +122,7 @@ def generate_card_grid(app, rst, projects, labels_path):
logger.warning(f"Thumbnail not found for {project_path}, skipping.")
continue # Skip if thumbnail doesn't exist

labels_str = generate_labels_rst(labels_path, section['labels'])
labels_str = generate_labels_rst(section['labels'])

last_updated_str = generate_last_updated_rst(section['last_updated'])

Expand All @@ -168,7 +149,6 @@ def generate_toctree(entries, hidden=True):
def generate_galleries(app):
gallery_conf = app.config.gallery_conf

labels_path = get_labels_path(app)
# Create category pages
category_projects = {}
for section in gallery_conf['sections']:
Expand All @@ -182,12 +162,12 @@ def generate_galleries(app):

for category in CATNAME_TO_CAT_MAP:
projects = category_projects.get(category, [])
generate_category_page(app, category, projects, labels_path)
generate_category_page(app, category, projects)

# Create main index.rst for gallery
generate_gallery_index(app, category_projects, labels_path)
generate_gallery_index(app, category_projects)

def generate_category_page(app, category, projects, labels_path):
def generate_category_page(app, category, projects):
# Main Header
rst = category + '\n' + '_'*len(category)*3 + '\n'

Expand All @@ -201,7 +181,7 @@ def generate_category_page(app, category, projects, labels_path):

if projects:
# Gallery Cards
rst, toctree_entries = generate_card_grid(app, rst, projects, labels_path)
rst, toctree_entries = generate_card_grid(app, rst, projects)
rst += generate_toctree(toctree_entries)

with open(os.path.join(app.builder.srcdir,
Expand Down Expand Up @@ -229,22 +209,24 @@ def generate_label_buttons(labels):
buttons_html += ' </div>\n</div>\n'
return buttons_html

def generate_gallery_index(app, category_projects, labels_path):
def generate_gallery_index(app, category_projects):
# Main Header
gallery_conf = app.config.gallery_conf
title = gallery_conf['title']
rst = title + '\n' + '_'*len(title)*3 + '\n'

# Overview
INTRO = os.path.join(app.builder.srcdir, f'intro.rst')
INTRO = os.path.join(app.builder.srcdir, 'intro.rst')
with open(INTRO, 'r') as file:
rst += '\n' + file.read() + '\n\n'

# Label Filter Buttons
all_labels = set()
all_labels = []
for sections in category_projects.values():
for section in sections:
all_labels.update(section['labels'])
all_labels.extend(section['labels'])
all_labels = Counter(all_labels)
all_labels = [label for label, _ in all_labels.most_common()]
label_buttons_html = generate_label_buttons(all_labels)

# Insert the label buttons using raw:: html
Expand All @@ -263,7 +245,7 @@ def generate_gallery_index(app, category_projects, labels_path):
if not projects:
continue

rst, _ = generate_card_grid(app, rst, projects, labels_path)
rst, _ = generate_card_grid(app, rst, projects)
rst += '\n\n'

toctree_entries.append(f'{category} <{category_link}>')
Expand Down
11 changes: 11 additions & 0 deletions _extensions/nbheader.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def load_authors_mapping(srcdir):


def create_header_html(
labels: list[str],
authors: list[dict[str, str]],
actions: list[dict[str, str]],
created_date,
Expand All @@ -40,6 +41,12 @@ def create_header_html(
updated_date = transform_date(updated_date)
if updated_date == created_date:
updated_date = None

labels = ''.join([
f'<span class="sd-sphinx-override sd-badge sd-outline-primary sd-text-primary me-2">{label}</span>'
for label in labels
])

authors_html = ''.join([
f'''
<div class="d-flex align-items-center">
Expand All @@ -62,6 +69,9 @@ def create_header_html(

return f'''
<div class="hv-nbheader container mb-5">
<div class="mb-2">
{labels}
</div>
<div class="hv-nbheader-authors-container mb-2">
{authors_html}
</div>
Expand Down Expand Up @@ -175,6 +185,7 @@ def add_nbheader(app):
actions.append(download)

html_header = create_header_html(
section['labels'],
authors_full,
actions,
header_data['created'],
Expand Down
5 changes: 5 additions & 0 deletions doc/_static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,8 @@ code.download.literal.xref.docutils {
color: #459eb9b3;
margin-top: 5px;
}

.hv-gallery-badges .sd-badge {
padding-left: 0.2em;
padding-right: 0.2em;
}
6 changes: 3 additions & 3 deletions doc/_static/js/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ document.addEventListener('DOMContentLoaded', function () {
allContainers.forEach(cardsContainer => {
const cards = Array.from(cardsContainer.getElementsByClassName('sd-col'));
cards.forEach(card => {
const labels = card.querySelectorAll('.sd-card-footer img');
const labels = card.querySelectorAll('span.sd-badge');
let matchedLabels = [];

labels.forEach(labelImg => {
const src = labelImg.src.split('/').pop().split('.')[0]; // Get the filename without extension
labels.forEach(labelBdg => {
const src = labelBdg.textContent
if (activeLabels.includes(src)) {
matchedLabels.push(src);
}
Expand Down
2 changes: 2 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ def gallery_spec(name):
categories = examples_config.get('categories', [])
# TODO: isn't optional
labels = examples_config.get('labels', [])
# labels always lower cased on the website
labels = list(map(str.lower, labels))
created = examples_config.get('created', 'NA')
authors = examples_config['maintainers']
# Optional, computed if not provided.
Expand Down
8 changes: 1 addition & 7 deletions dodo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1285,13 +1285,7 @@ def validate_project_file(name):
complain(f'{entry!r} must be a list')
if not all(isinstance(item, str) for item in value):
complain(f'all values of {value!r} must be a string')
if entry == 'labels':
labels_path = pathlib.Path('doc') / '_static' / 'labels'
labels = list(labels_path.glob('*.svg'))
for label in value:
if not any(label_file.stem == label for label_file in labels):
complain(f'missing {label}.svg file in doc/_static/labels')
elif entry == 'categories':
if entry == 'categories':
for cat in value:
if cat.lower() not in map(str.lower, CAT_TO_CATNAME_MAP):
complain(
Expand Down
3 changes: 0 additions & 3 deletions template/anaconda-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ examples_config:
categories:
- "Other Sciences"
# List of labels displayed in the project card
# Each label must be a name (e.g. panel) that
# refers to a SVG badge located in doc/_static/labels
# (e.g. doc/_static/labels/panel.svg)
labels:
- "hvplot"
- "panel"
Expand Down
Loading