diff --git a/build/local_examples.py b/build/local_examples.py new file mode 100644 index 0000000000..20ac1dd2f0 --- /dev/null +++ b/build/local_examples.py @@ -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") diff --git a/build/make.py b/build/make.py index b278806553..7e423cce9f 100644 --- a/build/make.py +++ b/build/make.py @@ -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: @@ -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)') diff --git a/layouts/partials/tabbed-clients-example.html b/layouts/partials/tabbed-clients-example.html index 3c79ea69ce..5d77838ad5 100644 --- a/layouts/partials/tabbed-clients-example.html +++ b/layouts/partials/tabbed-clients-example.html @@ -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 }} @@ -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 }} @@ -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 }} @@ -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 }} diff --git a/layouts/partials/tabs/wrapper.html b/layouts/partials/tabs/wrapper.html index bf9c18e536..47ae875021 100644 --- a/layouts/partials/tabs/wrapper.html +++ b/layouts/partials/tabs/wrapper.html @@ -21,7 +21,7 @@ hover:text-redis-red-600 rounded rounded-mx transition duration-150 ease-in-out" title="Open example" for="{{ $tid }}"> {{ if eq (index $tab "title") "redis-cli" }} - {{ $cliName }} + {{ or (index $tab "displayName") $cliName }} {{ else }} {{ index $tab "title" }} {{ end }} @@ -54,24 +54,44 @@
- - - - - + {{ $sourceUrl := index $tab "sourceUrl" }} + {{ if $sourceUrl }} + + + + + + {{ end }}
{{ end }} diff --git a/layouts/shortcodes/clients-example.html b/layouts/shortcodes/clients-example.html index fd56cb9c04..a0f199ba8c 100644 --- a/layouts/shortcodes/clients-example.html +++ b/layouts/shortcodes/clients-example.html @@ -2,5 +2,8 @@ {{ .Scratch.Set "step" (.Get 1) }} {{ .Scratch.Set "lang" (.Get 2) }} {{ .Scratch.Set "maxLines" (.Get 3) }} +{{ .Scratch.Set "cli_tab_name" (.Get 4) }} +{{ .Scratch.Set "cli_footer_link_text" (.Get 5) }} +{{ .Scratch.Set "cli_footer_link_url" (.Get 6) }} {{ .Scratch.Set "redisCommands" .Inner }} {{ partial "tabbed-clients-example.html" . }}