for-ais-only/tcedocs/README.md
The website redis.io is built from Markdown files using Hugo. Multi-language code example support is configured in Hugo by adding information to its configuration file, config.toml.
There are two sections that need to updated when new languages are added.
In the [params] section:
clientsExamples = ["Python", "Node.js", "Java-Sync", "Lettuce-Sync", "Java-Async", "Java-Reactive", "Go", "C", "C#-Sync", "C#-Async", "RedisVL", "PHP", "Rust-Sync", "Rust-Async"]
The order of the clientsExamples list matters: it's the order in which the language tabs are presented for each code example.
In the [params.clientsConfig] section:
[params.clientsConfig]
"Python"={quickstartSlug="redis-py"}
"Node.js"={quickstartSlug="nodejs"}
"Java-Sync"={quickstartSlug="jedis"}
"Lettuce-Sync"={quickstartSlug="lettuce"}
"Java-Async"={quickstartSlug="lettuce"}
"Java-Reactive"={quickstartSlug="lettuce"}
"Go"={quickstartSlug="go"}
"C"={quickstartSlug="hiredis"}
"C#-Sync"={quickstartSlug="dotnet"}
"C#-Async"={quickstartSlug="dotnet"}
"RedisVL"={quickstartSlug="redis-vl"}
"PHP"={quickstartSlug="php"}
"Rust-Sync"={quickstartSlug="rust"}
"Rust-Async"={quickstartSlug="rust"}
This configuration, along with the configuration steps below, is used to control the behavior of the Hugo shortcode that was developed to show tabbed code examples. A shortcode is a simple snippet inside a content file that Hugo will render using a predefined template. This template can contain HTML and JavaScript.
The folder data/components contains one component configuration file for each supported language. These files contain information about the GitHub repos that house the code examples.
Here is the configuration file for Python, redis_py.json:
{
"id": "redis_py",
"type": "client",
"name": "redis-py",
"language": "Python",
"label": "Python",
"repository": {
"git_uri": "https://github.com/redis/redis-py"
},
"examples": {
"git_uri": "https://github.com/redis/redis-py",
"path": "doctests",
"pattern": "*.py"
}
}
The language property needs to match the value that was added to the config.toml file in the previous step. The label property, while generally the same as language, may be set to a string that is different from language. For RedisVL, language is set to Python and label is set to RedisVL. The examples property points to a GitHub repository, a path under which examples should be searched, and a file name pattern. The current logic will scan for examples that fulfill the filename pattern within the given path.
Register your component file by adding it to the clients array in the index.json file, which resides in the the same folder as the per-language JSON files. The entry should match the file name prefix and ID of the component.
Here is an example:
"clients": [
"nredisstack_sync",
"nredisstack_async",
"go_redis",
"node_redis",
"php",
"redis_py",
"jedis",
"lettuce_sync",
"lettuce_async",
"lettuce_reactive",
"redis_vl",
"redis_rs_sync",
"redis_rs_async",
"hi_redis"
]
Code examples are pulled from the GitHub repo for each supported language at docs site build time.
Component handling is implemented in build/components/component.py. The example file parser that is used by it is implemented inside build/components/example.py. Add any language-specific information you need to have the build code support your language's examples.
TEST_MARKER = {
'java': '@Test',
'java-sync': '@Test',
'java-async': '@Test',
'java-reactive': '@Test',
'c#': r'\[Fact]|\[SkipIfRedis\(.*\)]'
}
PREFIXES = {
'python': '#',
'node.js': '//',
'java': '//',
'java-sync': '//',
'java-async': '//',
'java-reactive': '//',
'go': '//',
'c': '//',
'c#': '//',
'redisvl': '#',
'php': '//',
'rust': '//'
}
The TEST_MARKER dictionary maps programming languages to test framework annotations, which allows the parser to filter such source code lines out. The PREFIXES dictionary maps each language to its comment prefix. Python, for example, uses a hashtag (#) to start a comment.
⚠️ CRITICAL: The PREFIXES dictionary is essential for the example parser to work. If you add a new language, you must add an entry to this dictionary, or examples will fail to process with an "Unknown language" error. This is the most commonly missed step when adding a new language.
Each code example uses special comments, such as HIDE_START and REMOVE_START, to control how the examples are displayed. The following list gives an explanation:
EXAMPLE: id: Defines the identifier of the source code example file, where id is any common string (for example, cmds_string). IDs should only contain ASCII alphanumeric characters, underline characters (_), or hyphen characters (-). Do not use multibyte characters.BINDER_ID id: Defines the BinderHub commit hash for the example. This is used to generate a link to a BinderHub instance that will run the example.HIDE_START: Starts a code block that should be hidden when showing the example. This code block will only become visible if unhide (the eye button) is clicked.HIDE_END: Marks the end a hidden code block.REMOVE_START: Starts a code block that should be entirely removed when the example is processed by the build code. This is useful for removing lines of code that do not contribute to the example but are needed to embed the code into a proper test case or framework. Good examples of such code blocks are imports of external libraries or test assertions.REMOVE_END: Marks the end of a code block that should be removed from the example.STEP_START step-name: Starts a code block that represents a specific step in a set of examples.STEP_END: Marks the end of a code block that represents a specific step in a set of examples.Examples are added to either a client repo, or, temporarily, to the local_examples directory in the redis.io/docs repo.
Add a source code file to an appropriate client repo. Consult the /data/components/<client-component>.json file for the location.
| Programming Language | GitHub Repo | Default directory |
|---|---|---|
| C | hiredis | examples |
| C# | NRedisStack | tests/Doc |
| Go | go-redis | doctests |
| Java | jedis | src/test/java/io/redis/examples |
| Lettuce | src/test/java/io/redis/examples/sync, | |
src/test/java/io/redis/examples/async, or | ||
src/test/java/io/redis/examples/reactive | ||
| Node.js | node-redis | doctests |
| PHP | Predis | Examples, for now, are stored in local_examples |
| Python | redis-py | doctests |
| RedisVL | doctests |
At times, it can take quite a while to get new or updated examples through the review process. To make the examples available immediately on the docs site, you can place examples temporarily in the local_examples/client-specific directory. The manner in which files are added isn't terribly important, as the build code will recursively walk the entire directory, so it will find examples in any directory under local_examples.
local_examples
├── client-specific
│ ├── go
│ ├── c
│ │ ...
│ │ ...
│ ├── jedis
│ │ ...
│ ├── lettuce-sync
│ │ ...
│ ├── lettuce-async
│ │ ...
│ ├── lettuce-reactive
│ │ ...
│ ├── nodejs
│ │ ...
│ └── redis-py
│ ...
In order to add a multi-language code example to a content page, use the clients-example shortcode:
{{< clients-example id ... />}}
The ID is the same one you used with EXAMPLE: id in the first line of your code example.
The clients-example shortcode supports both positional and named parameters. The lion's share of current examples use positional parameters, but, going forward, names parameters should be used.
Named parameters:
buildsUpon="set_get" means this example requires understanding the "set_get" example firstbuildsUpon="lpush_rpush, lpop_rpop" indicates this example builds on bothPositional parameters (for backward compatibility):
The description parameter should provide clear, actionable guidance about what the example demonstrates and when to use it. A well-written description follows this pattern:
Pattern: [Category]: [What it does] [When/why to use it]
Examples:
"Foundational: Add members to a sorted set with their scores (creates new members or updates existing ones)""Query by score range: Retrieve members within a score range using ZRANGEBYSCORE when you need to filter by numeric values""Practical pattern: Use ZADD to set scores and ZINCRBY to increment them when you need to update leaderboards with atomic operations"Best practices:
The difficulty parameter helps readers find examples appropriate to their skill level. Use these guidelines:
Beginner (default):
ZADD, ZRANGE, ZRANGE ... WITHSCORESIntermediate:
-inf, +inf)ZRANGEBYSCORE, ZREMRANGEBYSCORE, ZRANK, ZRANGEBYLEX, leaderboard patternsAdvanced:
Decision tree:
Important: Difficulty should reflect not just syntax complexity, but also:
buildsUponThe buildsUpon parameter helps readers understand the learning progression and dependencies between examples. Use it to indicate when an example requires understanding a previous example first.
When to use buildsUpon:
When NOT to use buildsUpon:
Common patterns:
Linear progression: Each example builds on the previous one
{{< clients-example set="set_tutorial" step="set_get" ... >}}
{{< clients-example set="set_tutorial" step="setnx_xx" buildsUpon="set_get" ... >}}
{{< clients-example set="set_tutorial" step="mset" buildsUpon="set_get" ... >}}
Branching: Multiple examples build on a single foundational example
{{< clients-example set="list_tutorial" step="lpush_rpush" ... >}}
{{< clients-example set="list_tutorial" step="lpop_rpop" buildsUpon="lpush_rpush" ... >}}
{{< clients-example set="list_tutorial" step="lrange" buildsUpon="lpush_rpush" ... >}}
Multi-step chains: An example builds on multiple prerequisites
{{< clients-example set="list_tutorial" step="advanced_pattern" buildsUpon="lpush_rpush, lpop_rpop" ... >}}
Best practices:
"step1, step2"When writing descriptions, focus on the decision context rather than just what the command does. This helps readers understand when to use each pattern.
Anti-pattern (command-focused):
Better pattern (decision-focused):
Key phrases to use:
When converting existing content with redis-cli examples to the new format, you can wrap the existing redis-cli example:
{{< clients-example set="set_and_get" step="" >}}
> set mykey somevalue
OK
> get mykey
"somevalue"
{{< /clients-example >}}
If the redis-cli example is too long you can hide some lines by specifying the limit as the fourth argument:
{{< clients-example set="set_and_get" step="" lang_filter="" max_lines="2" >}}
> set mykey somevalue
OK
> get mykey <-- this line will be hidden
"somevalue" <-- this line will be hidden
{{< /clients-example >}}
To refer to a particular step placed in between STEP_START stepname and STEP_END comments in the code example, you should use the second argument to define the name of the step:
{{< clients-example set="id" step="stepname" />}}
If you need to embed an example for a specific programming language, the third argument should be defined:
{{< clients-example set="id" step="stepname" lang_filter="lang" />}}
The following example shows the connect step of a Python example:
{{< clients-example set="set_and_get" step="connect" lang_filter="Python" />}}
The programming language name should match the value in the Hugo configuration file.
The lang_filter parameter accepts a comma-separated list of exact language names:
{{< clients-example set="example_id" lang_filter="C#-Sync,C#-Async" />}}
Important: Language names must match exactly as they appear in config.toml. The matching is exact, not substring-based:
lang_filter="C#-Sync,C#-Async" - Shows only C# sync and async tabslang_filter="C" - Does NOT match "C#-Sync" or "C#-Async" (exact match only)lang_filter="Python,Node.js" - Shows Python and Node.js tabslang_filter="Python,Node" - Does NOT match "Node.js" (must use exact name)Common pitfall: If you use a language name that is a substring of another language name (e.g., "C" is a substring of "C#-Sync"), the filter will NOT accidentally match the longer name. Each language in the filter list must match exactly.
Symptom: The "Run in browser" link appears only after manually changing the language selector.
Root cause: The default selected tab (redis-cli) doesn't have a binderId. The JavaScript needs to search for the first tab that has a binderId on initial page load.
Solution: Ensure the JavaScript in layouts/partials/tabs/wrapper.html uses an isInitialLoad flag to enable fallback search on page load only:
let isInitialLoad = true;
function updateBinderLink() {
// ... get current panel and binderId ...
// On initial page load, if current tab doesn't have a binderId, find the first tab that does
if (isInitialLoad && !binderId) {
for (let i = 0; i < panels.length; i++) {
const panelBinderId = panels[i].getAttribute('data-binder-id');
if (panelBinderId) {
binderId = panelBinderId;
break;
}
}
}
// ... show link if binderId exists ...
}
// Initialize on page load
updateBinderLink();
isInitialLoad = false; // Set to false after first call
Symptom: Selecting a language without a notebook (e.g., Java-Async) still shows a "Run in browser" link pointing to a different language's notebook.
Root cause: The JavaScript is using fallback search on every update, not just on page load. This causes it to show the wrong language's link.
Solution: Only enable fallback search on initial page load (see above). After the first call, isInitialLoad is set to false, so subsequent language changes will only show the link if the selected language has a binderId.
Symptom: The link URL is incorrect or points to the wrong example.
Root cause: The binderId value in data/examples.json is incorrect or missing.
Solution:
BINDER_ID marker is present in your example source fileredis/binder-launchers repositorypython build/make.pybinderId appears in data/examples.json for your example