Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
158 changes: 158 additions & 0 deletions build/local_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
Local Examples Processor

This script processes local examples from the local_examples/ directory
and integrates them into the existing examples system.

Works like remote examples - each file contains an EXAMPLE: header
and can be any supported language.
"""

import os
import glob
import shutil
import logging
from typing import Dict, Any

from components.example import Example
from components.util import mkdir_p
from components.structured_data import load_dict, dump_dict


# File extension to language mapping
EXTENSION_TO_LANGUAGE = {
'.py': 'python',
'.js': 'node.js',
'.go': 'go',
'.cs': 'c#',
'.java': 'java',
'.php': 'php'
}

# Language to client name mapping (from config.toml clientsExamples)
LANGUAGE_TO_CLIENT = {
'python': 'Python',
'node.js': 'Node.js',
'go': 'Go',
'c#': 'C#',
'java': 'Java-Sync', # Default to sync, could be overridden
'php': 'PHP',
'redisvl': 'RedisVL'
}


def get_language_from_extension(filename: str) -> str:
"""Get language from file extension."""
_, ext = os.path.splitext(filename)
return EXTENSION_TO_LANGUAGE.get(ext.lower())


def get_client_name_from_language(language: str) -> str:
"""Get client name from language."""
return LANGUAGE_TO_CLIENT.get(language, language.title())


def get_example_id_from_file(path: str) -> str:
"""Extract example ID from the first line of a file."""
try:
with open(path, 'r') as f:
first_line = f.readline().strip()
if 'EXAMPLE:' in first_line:
return first_line.split(':')[1].strip()
except Exception as e:
logging.error(f"Error reading example ID from {path}: {e}")
return None


def process_local_examples(local_examples_dir: str = 'local_examples',
examples_dir: str = 'examples',
examples_json: str = 'data/examples.json') -> None:
"""
Process local examples and integrate them into the examples system.

Works like remote examples - each file contains an EXAMPLE: header
and can be any supported language.

Args:
local_examples_dir: Directory containing local example source files
examples_dir: Target directory for processed examples
examples_json: Path to examples.json file
"""

if not os.path.exists(local_examples_dir):
logging.info(f"Local examples directory {local_examples_dir} not found, skipping")
return

# Load existing examples data
examples_data = {}
if os.path.exists(examples_json):
examples_data = load_dict(examples_json)

# Process each file in local_examples directory
for filename in os.listdir(local_examples_dir):
source_file = os.path.join(local_examples_dir, filename)

if not os.path.isfile(source_file):
continue

# Get language from file extension
language = get_language_from_extension(filename)
if not language:
logging.warning(f"Unknown file extension for: {filename}")
continue

# Get example ID from file content
example_id = get_example_id_from_file(source_file)
if not example_id:
logging.warning(f"No EXAMPLE: header found in {filename}")
continue

logging.info(f"Processing local example: {example_id} ({language})")

# Create target directory
target_dir = os.path.join(examples_dir, example_id)
mkdir_p(target_dir)

# Initialize example data
if example_id not in examples_data:
examples_data[example_id] = {}

# Copy file to target directory with local_ prefix
base_name = os.path.splitext(filename)[0]
ext = os.path.splitext(filename)[1]
target_filename = f"local_{base_name}{ext}"
target_file = os.path.join(target_dir, target_filename)
shutil.copy2(source_file, target_file)

# Process with Example class
example = Example(language, target_file)

# Get client name
client_name = get_client_name_from_language(language)

# Create metadata
example_metadata = {
'source': source_file,
'language': language,
'target': target_file,
'highlight': example.highlight,
'hidden': example.hidden,
'named_steps': example.named_steps,
'sourceUrl': None # Local examples don't have source URLs
}

examples_data[example_id][client_name] = example_metadata
logging.info(f"Processed {client_name} example for {example_id}")

# Save updated examples data
dump_dict(examples_json, examples_data)
logging.info(f"Updated examples data saved to {examples_json}")


if __name__ == '__main__':
logging.basicConfig(level=logging.INFO,
format='%(levelname)s: %(message)s')

process_local_examples()
print("Local examples processing complete")
17 changes: 9 additions & 8 deletions build/make.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import argparse
from datetime import datetime
import logging
import sys
import tempfile

from components.component import All
from components.util import mkdir_p
from local_examples import process_local_examples


def parse_args() -> argparse.Namespace:
Expand All @@ -30,20 +32,19 @@ def parse_args() -> argparse.Namespace:
ARGS = parse_args()
mkdir_p(ARGS.tempdir)

# Configure logging BEFORE creating objects
log_level = getattr(logging, ARGS.loglevel.upper())
logging.basicConfig(
level=log_level,
format='%(message)s %(filename)s:%(lineno)d - %(funcName)s',
force=True # Force reconfiguration in case logging was already configured
)

# Load settings
ALL = All(ARGS.stack, None, ARGS.__dict__)

# Make the stack
logging.basicConfig(
level=ARGS.loglevel, format=f'{sys.argv[0]}: %(levelname)s %(asctime)s %(message)s')
print(f'Applying all configured components"{ALL._name}"')
start = datetime.now()
ALL.apply()

# Process local examples
print('Processing local examples')
process_local_examples()

total = datetime.now() - start
print(f'+OK ({total.microseconds / 1000} ms)')
92 changes: 92 additions & 0 deletions content/develop/clients/test-local-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: "Test Local Examples"
description: "Testing the local code examples feature"
---

# Test Local Examples

This page demonstrates the new local code examples feature that works just like remote examples.

## String Operations

Here's a local example showing string operations:

{{< clients-example local_string_demo incr >}}
> SET mykey "Hello Redis!"
OK
> GET mykey
"Hello Redis!"
> SET counter 10
OK
> INCR counter
(integer) 11
> INCRBY counter 5
(integer) 16
{{< /clients-example >}}

## With Step Highlighting

You can highlight specific steps:

{{< clients-example local_string_demo set_get >}}
> SET mykey "Hello Redis!"
OK
> GET mykey
"Hello Redis!"
{{< /clients-example >}}

## Hash Operations

Here's a local example for hash operations:

{{< clients-example local_hash_demo >}}
> HSET user:1000 name "John Smith"
(integer) 1
> HSET user:1000 email "[email protected]" age 30
(integer) 2
> HGET user:1000 name
"John Smith"
> HGETALL user:1000
1) "name"
2) "John Smith"
3) "email"
4) "[email protected]"
5) "age"
6) "30"
{{< /clients-example >}}

## List Operations (Go only)

This example only has a Go implementation:

{{< clients-example local_list_demo >}}
> LPUSH mylist "world"
(integer) 1
> LPUSH mylist "hello"
(integer) 2
> LRANGE mylist 0 -1
1) "hello"
2) "world"
{{< /clients-example >}}

## Language Filtering

Show only Python examples:

{{< clients-example local_hash_demo hset_hget Python >}}
> SET counter 10
OK
> INCR counter
(integer) 11
{{< /clients-example >}}

## Custom Tab Name and Footer Link

Test custom tab name and footer link (no language filtering so footer shows):

{{< clients-example local_string_demo "" "" "" "REST API" "LangCache API" "/develop/ai/langcache/api-reference" >}}
> SET counter 10
OK
> INCR counter
(integer) 11
{{< /clients-example >}}
13 changes: 8 additions & 5 deletions layouts/partials/tabbed-clients-example.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
{{ $lang := .Scratch.Get "lang" }}
{{ $redisCommands := .Scratch.Get "redisCommands" }}
{{ $redisCommandsLineLimit := (or (.Scratch.Get "maxLines") 100) }}
{{ $cliTabName := (or (.Scratch.Get "cli_tab_name") ">_ Redis CLI") }}
{{ $cliFooterLinkText := .Scratch.Get "cli_footer_link_text" }}
{{ $cliFooterLinkUrl := .Scratch.Get "cli_footer_link_url" }}

{{ if not (isset $.Site.Data.examples $id) }}
{{ warnf "[tabbed-clients-example] Example not found %q for %q" $id $.Page }}
Expand All @@ -12,7 +15,7 @@
{{/* Render redis-cli example from inner content if any */}}
{{ if (ne (trim $redisCommands "\n") "") }}
{{ $redisCliContent := highlight (trim $redisCommands "\n") "plaintext" (printf "linenos=false,hl_lines=1-%d" $redisCommandsLineLimit ) }}
{{ $tabs = $tabs | append (dict "title" "redis-cli" "content" $redisCliContent "limit" $redisCommandsLineLimit) }}
{{ $tabs = $tabs | append (dict "title" "redis-cli" "displayName" $cliTabName "content" $redisCliContent "limit" $redisCommandsLineLimit "customFooterLinkText" $cliFooterLinkText "customFooterLinkUrl" $cliFooterLinkUrl) }}
{{ end }}

{{ $clientExamples := index $.Site.Data.examples $id }}
Expand All @@ -21,15 +24,15 @@
{{ $clientConfig := index $.Site.Params.clientsconfig $client }}
{{ $language := index $example "language" }}
{{ $quickstartSlug := index $clientConfig "quickstartSlug" }}

{{ if and ($example) (or (eq $lang "") (strings.Contains $lang $client)) }}
{{ $examplePath := index $example "target" }}
{{ $options := printf "linenos=false" }}

{{ if and (ne $step "") (isset $example "named_steps") (isset $example.named_steps $step) }}
{{ $options = printf "%s,hl_lines=%s" $options (index $example.named_steps $step) }}
{{ else }}
{{ if isset $example "highlight" }}
{{ if and (isset $example "highlight") (index $example "highlight") }}
{{ $options = printf "%s,hl_lines=%s" $options (delimit (index $example "highlight") " ") }}
{{ end }}
{{ end }}
Expand All @@ -40,5 +43,5 @@
{{ end }}
{{ end }}

{{ $params := dict "id" (printf "%s-step%s" $id $step) "tabs" $tabs "showFooter" (eq $lang "") }}
{{ $params := dict "id" (printf "%s-step%s" $id $step) "tabs" $tabs "showFooter" true }}
{{ partial "tabs/wrapper.html" $params }}
Loading