Skip to content

Commit 2ed7124

Browse files
DOC-5804 updated format and added Node.js landing page
1 parent 0456155 commit 2ed7124

File tree

6 files changed

+186
-69
lines changed

6 files changed

+186
-69
lines changed

build/components/example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def make_ranges(self) -> None:
9191
rstart = re.compile(f'{PREFIXES[self.language]}\\s?{REMOVE_START}')
9292
rend = re.compile(f'{PREFIXES[self.language]}\\s?{REMOVE_END}')
9393
exid = re.compile(f'{PREFIXES[self.language]}\\s?{EXAMPLE}')
94-
binder = re.compile(f'{PREFIXES[self.language]}\\s?{BINDER_ID}\\s+([a-f0-9]{{40}})')
94+
binder = re.compile(f'{PREFIXES[self.language]}\\s?{BINDER_ID}\\s+([a-zA-Z0-9_-]+)')
9595
go_output = re.compile(f'{PREFIXES[self.language]}\\s?{GO_OUTPUT}')
9696
go_comment = re.compile(f'{PREFIXES[self.language]}')
9797
test_marker = re.compile(f'{TEST_MARKER.get(self.language)}')

build/tcedocs/SPECIFICATION.md

Lines changed: 135 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,19 @@ This allows documentation to include "Try this in Jupyter" links that launch int
344344

345345
**BINDER_ID Extraction Details**:
346346

347+
The `BINDER_ID` marker allows example authors to specify a Git reference (branch name or commit SHA) from the `redis/binder-launchers` repository. This enables the Hugo templates to generate "Run this example in the browser" links that open the example in an interactive Jupyter notebook environment via BinderHub.
348+
349+
**Quick Implementation Checklist**:
350+
- [ ] Add constant: `BINDER_ID = 'BINDER_ID'` (around line 11 in `example.py`)
351+
- [ ] Add class attribute: `binder_id = None` (around line 49 in `Example` class)
352+
- [ ] Add regex pattern: `binder = re.compile(...)` (around line 94 in `make_ranges()`)
353+
- [ ] Add detection logic in `elif` chain (around line 157 in main loop)
354+
- [ ] Add conditional metadata field in `build/local_examples.py` (around line 183)
355+
- [ ] Add conditional metadata field in `build/components/component.py` (around line 278)
356+
- [ ] Test with both branch name and commit SHA
357+
- [ ] Verify `BINDER_ID` line removed from processed output
358+
- [ ] Verify `binderId` appears in `data/examples.json`
359+
347360
The parser should implement the following logic in `build/components/example.py`:
348361

349362
**1. Add Constant and Class Attribute**:
@@ -372,30 +385,40 @@ self.binder_id = None
372385

373386
**2. Compile Regex Pattern**:
374387

375-
In the `make_ranges()` method, add the regex pattern compilation alongside other patterns (after `exid` pattern):
388+
In the `make_ranges()` method (around line 94), add the regex pattern compilation alongside other patterns (after `exid` pattern):
376389
```python
377390
exid = re.compile(f'{PREFIXES[self.language]}\\s?{EXAMPLE}')
378-
binder = re.compile(f'{PREFIXES[self.language]}\\s?{BINDER_ID}\\s+([a-f0-9]{{40}})')
391+
binder = re.compile(f'{PREFIXES[self.language]}\\s?{BINDER_ID}\\s+([a-zA-Z0-9_-]+)')
379392
go_output = re.compile(f'{PREFIXES[self.language]}\\s?{GO_OUTPUT}')
380393
```
381394

395+
**Exact location**: In `build/components/example.py`, class `Example`, method `make_ranges()`, in the section where regex patterns are compiled (after line 93).
396+
382397
**Pattern explanation**:
383398
- `{PREFIXES[self.language]}` - Language-specific comment prefix (e.g., `#` or `//`)
384399
- `\\s?` - Optional whitespace after comment prefix
385400
- `{BINDER_ID}` - The literal string "BINDER_ID"
386-
- `\\s+` - Required whitespace before hash
387-
- `([a-f0-9]{40})` - Capture group for exactly 40 hexadecimal characters
401+
- `\\s+` - Required whitespace before identifier
402+
- `([a-zA-Z0-9_-]+)` - Capture group for Git reference (commit SHA or branch name)
403+
- Matches: lowercase letters (a-z), uppercase letters (A-Z), digits (0-9), hyphens (-), underscores (_)
404+
- Length: 1 or more characters (no maximum)
405+
- Examples: `6bbed3da294e8de5a8c2ad99abf883731a50d4dd` (commit SHA), `python-landing` (branch name), `main`, `feature-123`
406+
407+
**Why this pattern works**:
408+
- **Backward compatible**: The old pattern `([a-f0-9]{40})` only matched commit SHAs. The new pattern `([a-zA-Z0-9_-]+)` matches commit SHAs (which are valid under the new pattern) AND branch names.
409+
- **No breaking changes**: Existing examples with commit SHAs continue to work without modification.
410+
- **Flexible**: Supports common Git branch naming conventions (kebab-case, snake_case, alphanumeric).
388411

389412
**3. Detection and Extraction**:
390413

391-
Add detection logic in the main processing loop, **after** the `EXAMPLE:` check and **before** the `GO_OUTPUT` check:
414+
Add detection logic in the main processing loop (around line 157), **after** the `EXAMPLE:` check and **before** the `GO_OUTPUT` check:
392415

393416
```python
394417
elif re.search(exid, l):
395418
output = False
396419
pass
397420
elif re.search(binder, l):
398-
# Extract BINDER_ID hash value
421+
# Extract BINDER_ID value (commit SHA or branch name)
399422
match = re.search(binder, l)
400423
if match:
401424
self.binder_id = match.group(1)
@@ -405,11 +428,14 @@ elif self.language == "go" and re.search(go_output, l):
405428
# ... rest of processing
406429
```
407430

431+
**Exact location**: In `build/components/example.py`, class `Example`, method `make_ranges()`, in the main `while curr < len(self.content):` loop, in the `elif` chain that handles special markers.
432+
408433
**Critical implementation details**:
409434
- **Must set `output = False`**: This prevents the line from being added to the `content` array
410435
- **Placement matters**: Must be in the `elif` chain, not a separate `if` statement
411436
- **No `content.append(l)`**: The line is skipped entirely, just like `EXAMPLE:` lines
412-
- **Extract before setting output**: Get the hash value before marking the line to skip
437+
- **Extract before setting output**: Get the value before marking the line to skip
438+
- **Order in elif chain**: Must come after `exid` (EXAMPLE:) but before `go_output` to maintain proper precedence
413439

414440
**4. Storage in Metadata**:
415441

@@ -469,6 +495,9 @@ The `BINDER_ID` line is removed from output through the same mechanism as other
469495
3. **Using `if` instead of `elif`**: Could cause multiple conditions to match
470496
4. **Not checking `if match`**: Could cause AttributeError if regex doesn't match
471497
5. **Adding field unconditionally**: Results in `"binderId": null` in JSON for examples without the marker
498+
6. **Regex pattern too restrictive**: Using `[a-f0-9]{40}` only matches commit SHAs, not branch names
499+
7. **Regex pattern too permissive**: Using `.*` or `.+` could match invalid characters or whitespace
500+
8. **Wrong capture group**: Using `match.group(0)` returns the entire match including comment prefix, not just the value
472501

473502
**6. Complete Example Flow**:
474503

@@ -477,7 +506,7 @@ Here's a complete example showing how a file is processed:
477506
**Input file** (`local_examples/client-specific/redis-py/landing.py`):
478507
```python
479508
# EXAMPLE: landing
480-
# BINDER_ID 6bbed3da294e8de5a8c2ad99abf883731a50d4dd
509+
# BINDER_ID python-landing
481510
import redis
482511

483512
# STEP_START connect
@@ -487,7 +516,7 @@ r = redis.Redis(host='localhost', port=6379, decode_responses=True)
487516

488517
**Processing steps**:
489518
1. Line 1: `EXAMPLE:` detected → `output = False` → line skipped
490-
2. Line 2: `BINDER_ID` detected → extract hash `6bbed3da294e8de5a8c2ad99abf883731a50d4dd``output = False` → line skipped
519+
2. Line 2: `BINDER_ID` detected → extract value `python-landing``output = False` → line skipped
491520
3. Line 3: `import redis` → no marker → added to `content` array at index 0
492521
4. Line 4: Empty line → added to `content` array at index 1
493522
5. Line 5: `STEP_START` detected → record step start at line 3 (len(content) + 1) → line skipped
@@ -515,7 +544,7 @@ r = redis.Redis(host='localhost', port=6379, decode_responses=True)
515544
"connect": "3-3"
516545
},
517546
"sourceUrl": null,
518-
"binderId": "6bbed3da294e8de5a8c2ad99abf883731a50d4dd"
547+
"binderId": "python-landing"
519548
}
520549
}
521550
}
@@ -525,7 +554,8 @@ r = redis.Redis(host='localhost', port=6379, decode_responses=True)
525554
- Both `EXAMPLE:` and `BINDER_ID` lines are removed from output
526555
- Line numbers in metadata refer to the processed file (after marker removal)
527556
- `binderId` is stored at the language level, not the example set level
528-
- The hash value is extracted cleanly without comment prefix or keyword
557+
- The value is extracted cleanly without comment prefix or keyword
558+
- Value can be either a Git commit SHA (40 hex chars) or a branch name (letters, numbers, hyphens, underscores)
529559

530560
**Output Metadata** (stored in `examples.json`):
531561
- `highlight`: Line ranges to highlight (e.g., `["1-10", "15-20"]`)
@@ -639,12 +669,18 @@ https://redis.io/binder/v2/gh/redis/binder-launchers/<binderId>?urlpath=%2Fdoc%2
639669

640670
**URL Components**:
641671
- **Base URL**: `https://redis.io/binder/v2/gh/redis/binder-launchers/`
642-
- **Binder ID**: The Git commit SHA from `binderId` field (40 hexadecimal characters)
672+
- **Binder ID**: The Git reference from `binderId` field (commit SHA or branch name)
673+
- **Commit SHA**: 40 hexadecimal characters (e.g., `6bbed3da294e8de5a8c2ad99abf883731a50d4dd`)
674+
- **Branch name**: Letters, numbers, hyphens, underscores (e.g., `python-landing`, `main`, `feature-123`)
643675
- **URL Path**: `?urlpath=%2Fdoc%2Ftree%2Fdemo.ipynb` (constant, URL-encoded path to notebook)
644676
- **Notebook filename**: Always `demo.ipynb` - do NOT change per example
645677

646-
**Example**:
678+
**Examples**:
647679
```
680+
# Using branch name
681+
https://redis.io/binder/v2/gh/redis/binder-launchers/python-landing?urlpath=%2Fdoc%2Ftree%2Fdemo.ipynb
682+
683+
# Using commit SHA
648684
https://redis.io/binder/v2/gh/redis/binder-launchers/6bbed3da294e8de5a8c2ad99abf883731a50d4dd?urlpath=%2Fdoc%2Ftree%2Fdemo.ipynb
649685
```
650686

@@ -1291,12 +1327,26 @@ The `redis/binder-launchers` repository contains Jupyter notebooks for each exam
12911327
1. Create a notebook file (e.g., `demo.ipynb`) that runs your example in the appropriate language
12921328
2. Ensure the necessary language kernel is configured in the BinderHub environment
12931329
3. Commit and push to the `redis/binder-launchers` repository
1294-
4. Note the commit SHA (40-character hexadecimal hash)
1330+
4. Choose your Git reference strategy:
1331+
- **Branch name** (recommended for active development): Use a descriptive branch name like `python-landing`, `main`, or `feature-xyz`
1332+
- **Commit SHA** (recommended for stable examples): Use the 40-character hexadecimal commit hash
12951333

12961334
**Step 2: Add BINDER_ID to your example**:
12971335

1298-
Add the `BINDER_ID` marker as the second line of your example file (after `EXAMPLE:`):
1336+
Add the `BINDER_ID` marker as the second line of your example file (after `EXAMPLE:`).
12991337

1338+
**Option A: Using a branch name** (recommended for active development):
1339+
```python
1340+
# EXAMPLE: my_new_example
1341+
# BINDER_ID python-landing
1342+
import redis
1343+
1344+
# STEP_START connect
1345+
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
1346+
# STEP_END
1347+
```
1348+
1349+
**Option B: Using a commit SHA** (recommended for stable examples):
13001350
```python
13011351
# EXAMPLE: my_new_example
13021352
# BINDER_ID 6bbed3da294e8de5a8c2ad99abf883731a50d4dd
@@ -1307,6 +1357,18 @@ r = redis.Redis(host='localhost', port=6379, decode_responses=True)
13071357
# STEP_END
13081358
```
13091359

1360+
**Choosing between branch name and commit SHA**:
1361+
1362+
| Aspect | Branch Name | Commit SHA |
1363+
|--------|-------------|------------|
1364+
| **Updates** | Automatically uses latest commit on branch | Fixed to specific commit |
1365+
| **Stability** | May change if branch is updated | Immutable, always same version |
1366+
| **Maintenance** | Easy - just push to branch | Requires updating `BINDER_ID` after each change |
1367+
| **Use case** | Active development, frequently updated examples | Stable, production examples |
1368+
| **Example** | `python-landing`, `main`, `dev` | `6bbed3da294e8de5a8c2ad99abf883731a50d4dd` |
1369+
1370+
**Recommendation**: Use branch names during development for easier iteration, then switch to commit SHAs when the example is stable and ready for production.
1371+
13101372
**Step 3: Rebuild and verify**:
13111373

13121374
```bash
@@ -1315,7 +1377,7 @@ python3 build/local_examples.py
13151377

13161378
# Verify binderId appears in metadata
13171379
python3 -c "import json; data = json.load(open('data/examples.json')); print(data['my_new_example']['Python'].get('binderId'))"
1318-
# Should output: 6bbed3da294e8de5a8c2ad99abf883731a50d4dd
1380+
# Should output: python-landing (or your commit SHA)
13191381

13201382
# Verify BINDER_ID line is removed from processed file
13211383
cat examples/my_new_example/local_*.py | grep BINDER_ID
@@ -1326,12 +1388,56 @@ hugo serve
13261388
# Navigate to the page and verify "Run this example in the browser" link appears
13271389
```
13281390

1391+
**Step 4: Test both formats** (recommended during development):
1392+
1393+
To ensure the regex pattern works correctly with both branch names and commit SHAs, create temporary test files:
1394+
1395+
```bash
1396+
# Test 1: Branch name
1397+
cat > local_examples/test_branch.py << 'EOF'
1398+
# EXAMPLE: test_branch
1399+
# BINDER_ID main
1400+
import redis
1401+
r = redis.Redis()
1402+
EOF
1403+
1404+
# Test 2: Commit SHA
1405+
cat > local_examples/test_sha.py << 'EOF'
1406+
# EXAMPLE: test_sha
1407+
# BINDER_ID 6bbed3da294e8de5a8c2ad99abf883731a50d4dd
1408+
import redis
1409+
r = redis.Redis()
1410+
EOF
1411+
1412+
# Process and verify both
1413+
python3 build/local_examples.py
1414+
1415+
# Check branch name extraction
1416+
python3 -c "import json; data = json.load(open('data/examples.json')); print('Branch:', data['test_branch']['Python'].get('binderId'))"
1417+
# Expected output: Branch: main
1418+
1419+
# Check commit SHA extraction
1420+
python3 -c "import json; data = json.load(open('data/examples.json')); print('SHA:', data['test_sha']['Python'].get('binderId'))"
1421+
# Expected output: SHA: 6bbed3da294e8de5a8c2ad99abf883731a50d4dd
1422+
1423+
# Verify both lines removed from processed files
1424+
grep BINDER_ID examples/test_branch/local_test_branch.py
1425+
grep BINDER_ID examples/test_sha/local_test_sha.py
1426+
# Both should output nothing
1427+
1428+
# Clean up test files
1429+
rm local_examples/test_branch.py local_examples/test_sha.py
1430+
python3 build/local_examples.py # Rebuild to remove from metadata
1431+
```
1432+
13291433
**Important notes**:
13301434
- BinderHub uses **Jupyter notebooks** which support multiple languages through kernels (Python, Node.js, Java, etc.)
1331-
- The commit hash must exist in the `redis/binder-launchers` repository
1435+
- The Git reference (branch or commit) must exist in the `redis/binder-launchers` repository
13321436
- The notebook filename is always `demo.ipynb` (hardcoded in the URL)
13331437
- The link will only appear if `binderId` exists in the metadata
1334-
- Update the `BINDER_ID` hash whenever you update the notebook in the launcher repository
1438+
- **Branch names**: Automatically use the latest commit on that branch (easier maintenance, but may change)
1439+
- **Commit SHAs**: Point to a specific immutable version (more stable, but requires manual updates)
1440+
- Update the `BINDER_ID` value whenever you want to point to a different version of the notebook
13351441
- Ensure the appropriate language kernel is installed in the BinderHub environment for your example's language
13361442

13371443
### When to Rebuild
@@ -1568,11 +1674,13 @@ ModuleNotFoundError: No module named 'pytoml'
15681674
- **Cause**: Regex pattern not matching the line
15691675
- **Debug**:
15701676
1. Check comment prefix matches language: `# BINDER_ID` for Python, `// BINDER_ID` for JavaScript
1571-
2. Verify hash is exactly 40 hexadecimal characters (lowercase a-f, 0-9)
1677+
2. Verify value format:
1678+
- **Branch name**: Letters, numbers, hyphens, underscores (e.g., `python-landing`, `main`)
1679+
- **Commit SHA**: Exactly 40 hexadecimal characters (e.g., `6bbed3da294e8de5a8c2ad99abf883731a50d4dd`)
15721680
3. Check for extra whitespace or special characters
15731681
4. Run with debug logging: `python3 build/local_examples.py --loglevel DEBUG`
15741682
5. Look for "Found BINDER_ID" message in logs
1575-
- **Fix**: Ensure format is exactly `{comment_prefix} BINDER_ID {40-char-hash}`
1683+
- **Fix**: Ensure format is exactly `{comment_prefix} BINDER_ID {git-reference}`
15761684

15771685
- **Symptom 2**: `BINDER_ID` line appears in processed output file
15781686
- **Cause**: `output = False` not set in detection logic
@@ -1587,10 +1695,11 @@ ModuleNotFoundError: No module named 'pytoml'
15871695
example_metadata['binderId'] = example.binder_id
15881696
```
15891697

1590-
- **Symptom 4**: Wrong hash value extracted
1698+
- **Symptom 4**: Wrong value extracted
15911699
- **Cause**: Regex capture group not matching correctly
1592-
- **Debug**: Check the regex pattern includes capture group: `([a-f0-9]{40})`
1593-
- **Fix**: Ensure using `match.group(1)` to extract the captured hash
1700+
- **Debug**: Check the regex pattern includes capture group: `([a-zA-Z0-9_-]+)`
1701+
- **Fix**: Ensure using `match.group(1)` to extract the captured value
1702+
- **Verify**: Value should match what's in the source file (branch name or commit SHA)
15941703

15951704
**BinderHub "Run in browser" link issues**:
15961705
- **Symptom 1**: Link not appearing in example box
@@ -1649,9 +1758,9 @@ ModuleNotFoundError: No module named 'pytoml'
16491758
1. Open browser console
16501759
2. Find the current panel: `document.querySelector('.panel:not(.panel-hidden)')`
16511760
3. Check attribute: `panel.getAttribute('data-binder-id')`
1652-
4. Should be 40-character hex string
1761+
4. Should be a valid Git reference (branch name or 40-character commit SHA)
16531762
- **If wrong**: Check metadata in `data/examples.json`
1654-
- **Fix**: Verify `BINDER_ID` in source file is correct commit hash
1763+
- **Fix**: Verify `BINDER_ID` in source file is correct (matches what's in `redis/binder-launchers` repo)
16551764

16561765
- **Symptom 3**: Link opens but BinderHub shows error
16571766
- **Cause 1**: Invalid commit hash in `binderId`
@@ -1982,7 +2091,7 @@ In Markdown files:
19822091
| Marker | Purpose | Example | Notes |
19832092
|--------|---------|---------|-------|
19842093
| `EXAMPLE: id` | Define example ID | `# EXAMPLE: home_vecsets` | **Required**. Must be first line. Removed from processed output. |
1985-
| `BINDER_ID hash` | Define BinderHub commit hash | `# BINDER_ID 6bbed3da294e8de5a8c2ad99abf883731a50d4dd` | **Optional**. Typically line 2 (after EXAMPLE). Hash must be exactly 40 hexadecimal characters (Git commit SHA). Removed from processed output. Stored as `binderId` in metadata. Used to generate interactive Jupyter notebook links. |
2094+
| `BINDER_ID ref` | Define BinderHub Git reference | `# BINDER_ID python-landing`<br>`# BINDER_ID 6bbed3da294e8de5a8c2ad99abf883731a50d4dd` | **Optional**. Typically line 2 (after EXAMPLE). Value can be a Git branch name (e.g., `python-landing`, `main`) or commit SHA (40 hex chars). Removed from processed output. Stored as `binderId` in metadata. Used to generate interactive Jupyter notebook links. |
19862095
| `HIDE_START` | Start hidden block | `# HIDE_START` | Code hidden by default, revealed with eye button |
19872096
| `HIDE_END` | End hidden block | `# HIDE_END` | Must close HIDE_START |
19882097
| `REMOVE_START` | Start removed block | `# REMOVE_START` | Code completely removed from output |

content/develop/clients/nodejs/_index.md

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,44 +38,18 @@ npm install redis
3838

3939
Connect to localhost on port 6379.
4040

41-
```js
42-
import { createClient } from 'redis';
43-
44-
const client = createClient();
45-
46-
client.on('error', err => console.log('Redis Client Error', err));
47-
48-
await client.connect();
49-
```
41+
{{< clients-example set="landing" step="connect" lang_filter="Node.js" >}}
42+
{{< /clients-example >}}
5043

5144
Store and retrieve a simple string.
5245

53-
```js
54-
await client.set('key', 'value');
55-
const value = await client.get('key');
56-
```
46+
{{< clients-example set="landing" step="set_get_string" lang_filter="Node.js" >}}
47+
{{< /clients-example >}}
5748

5849
Store and retrieve a map.
5950

60-
```js
61-
await client.hSet('user-session:123', {
62-
name: 'John',
63-
surname: 'Smith',
64-
company: 'Redis',
65-
age: 29
66-
})
67-
68-
let userSession = await client.hGetAll('user-session:123');
69-
console.log(JSON.stringify(userSession, null, 2));
70-
/*
71-
{
72-
"surname": "Smith",
73-
"name": "John",
74-
"company": "Redis",
75-
"age": "29"
76-
}
77-
*/
78-
```
51+
{{< clients-example set="landing" step="set_get_hash" lang_filter="Node.js" >}}
52+
{{< /clients-example >}}
7953

8054
To connect to a different host or port, use a connection string in the format `redis[s]://[[username][:password]@][host][:port][/db-number]`:
8155

@@ -88,9 +62,8 @@ To check if the client is connected and ready to send commands, use `client.isRe
8862

8963
When you have finished using a connection, close it with `client.quit()`.
9064

91-
```js
92-
await client.quit();
93-
```
65+
{{< clients-example set="landing" step="close" lang_filter="Node.js" >}}
66+
{{< /clients-example >}}
9467

9568
## More information
9669

0 commit comments

Comments
 (0)