Skip to content
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
49 changes: 49 additions & 0 deletions build/jupyterize/SPECIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,8 @@ Go has a unique requirement: notebooks MUST have a `func main() {}` wrapper for
- This ensures imports and `func main() {}` are in the same cell, matching gophernotes expectations
- The wrapper is injected as boilerplate, then removed from source, then re-injected
- This ensures the notebook has a clean wrapper without test framework code
- **Client connection configuration**: Boilerplate can include more than just wrappers—it can include client connection setup (e.g., Redis client initialization with configuration options like `MaintNotificationsConfig`)
- This allows notebooks to have a working client connection without requiring users to add boilerplate code manually

**Pattern complexity comparison** (As Proposed):
- **C#**: 5 patterns (class/method wrappers + closing braces)
Expand Down Expand Up @@ -1363,6 +1365,53 @@ When adding a new language, ask:

If yes to any of these, use Strategy 2 (append to first cell). Otherwise, use Strategy 1 (separate cell).

### Boilerplate Content Patterns (What Goes in Boilerplate)

**Lesson Learned**: Boilerplate is not limited to language wrappers and imports. It can include any code that should appear in every notebook generated from that language, including client connection setup and configuration.

**Common boilerplate content**:
1. **Language wrappers** (required by kernel)
- C#: NuGet package directives (`#r "nuget: ..."`)
- Go: `func main() {}` wrapper (gophernotes requirement)
- Java: (typically empty; dependencies handled via `%maven` magic commands)

2. **Client connection setup** (optional but recommended)
- Redis client initialization with connection options
- Configuration flags (e.g., `MaintNotificationsConfig` for Go)
- Default connection parameters (host, port, password)
- This ensures notebooks have a working client without manual setup

3. **Import statements** (language-dependent)
- C#: NuGet directives (not traditional imports)
- Go: Typically in STEP blocks, not boilerplate (unless needed for wrapper)
- Java: Typically in STEP blocks, not boilerplate

**Example: Go with Client Connection**:
```json
{
"go": {
"boilerplate": [
"rdb := redis.NewClient(&redis.Options{",
"\tAddr: \"localhost:6379\",",
"\tPassword: \"\",",
"\tDB: 0,",
"\tMaintNotificationsConfig: &maintnotifications.Config{",
"\t\tMode: maintnotifications.ModeDisabled,",
"\t},",
"})",
"func main() {}"
]
}
}
```

**Design considerations**:
- **Boilerplate should be minimal**: Include only code that's needed in every notebook
- **Avoid language-specific details**: Don't include code that only applies to some examples
- **Configuration over code**: Use boilerplate for configuration (connection options) rather than business logic
- **Test with real examples**: Verify boilerplate works with actual example files before committing
- **Document assumptions**: If boilerplate assumes certain imports or packages, document this in comments or README


### Testing Checklist (Language-Specific)

Expand Down
8 changes: 8 additions & 0 deletions build/jupyterize/jupyterize_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@
},
"go": {
"boilerplate": [
"rdb := redis.NewClient(&redis.Options{",
"\tAddr: \"localhost:6379\",",
"\tPassword: \"\",",
"\tDB: 0,",
"\tMaintNotificationsConfig: &maintnotifications.Config{",
"\t\tMode: maintnotifications.ModeDisabled,",
"\t},",
"})",
"func main() {}"
],
"unwrap_patterns": [
Expand Down
8 changes: 6 additions & 2 deletions build/jupyterize/test_jupyterize.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ def test_python_no_boilerplate():


def test_go_boilerplate_injection():
"""Test Go boilerplate injection (func main() {} appended to first cell)."""
"""Test Go boilerplate injection (client config and func main() {} appended to first cell)."""
print("\nTesting Go boilerplate injection...")

# Create test file with Go code
Expand Down Expand Up @@ -556,12 +556,16 @@ def test_go_boilerplate_injection():
assert nb['metadata']['kernelspec']['name'] == 'gophernotes', \
f"Kernel should be gophernotes, got {nb['metadata']['kernelspec']['name']}"

# First cell should contain imports AND func main() {}
# First cell should contain imports, client config, and func main() {}
# (boilerplate is appended to first cell for Go, not separate)
first_cell = nb['cells'][0]
first_cell_source = ''.join(first_cell['source'])
assert 'import (' in first_cell_source, \
f"First cell should contain imports, got: {first_cell_source}"
assert 'redis.NewClient' in first_cell_source, \
f"First cell should contain redis.NewClient, got: {first_cell_source}"
assert 'MaintNotificationsConfig' in first_cell_source, \
f"First cell should contain MaintNotificationsConfig, got: {first_cell_source}"
assert 'func main() {}' in first_cell_source, \
f"First cell should contain 'func main() {{}}', got: {first_cell_source}"

Expand Down
25 changes: 21 additions & 4 deletions content/develop/data-types/lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ See the [complete series of list commands]({{< relref "/commands/" >}}?group=lis

* To limit the length of a list you can call [`LTRIM`]({{< relref "/commands/ltrim" >}}):
{{< clients-example list_tutorial ltrim.1 >}}
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
> LTRIM bikes:repairs 0 2
Expand All @@ -103,6 +105,7 @@ OK
{{< /clients-example >}}

### What are Lists?

To explain the List data type it's better to start with a little bit of theory,
as the term *List* is often used in an improper way by information technology
folks. For instance "Python Lists" are not what the name may suggest (Linked
Expand Down Expand Up @@ -143,6 +146,8 @@ element into a list, on the right (at the tail). Finally the
[`LRANGE`]({{< relref "/commands/lrange" >}}) command extracts ranges of elements from lists:

{{< clients-example list_tutorial lpush_rpush >}}
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1
(integer) 1
> RPUSH bikes:repairs bike:2
Expand All @@ -167,6 +172,8 @@ Both commands are *variadic commands*, meaning that you are free to push
multiple elements into a list in a single call:

{{< clients-example list_tutorial variadic >}}
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
> LPUSH bikes:repairs bike:important_bike bike:very_important_bike
Expand All @@ -187,6 +194,8 @@ sequence of commands the list is empty and there are no more elements to
pop:

{{< clients-example list_tutorial lpop_rpop >}}
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
> RPOP bikes:repairs
Expand Down Expand Up @@ -239,6 +248,8 @@ For example, if you're adding bikes on the end of a list of repairs, but only
want to worry about the 3 that have been on the list the longest:

{{< clients-example list_tutorial ltrim >}}
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
> LTRIM bikes:repairs 0 2
Expand All @@ -256,6 +267,8 @@ to add a new element and discard elements exceeding a limit. Using
[`LTRIM`]({{< relref "/commands/ltrim" >}}) with negative indexes can then be used to keep only the 3 most recently added:

{{< clients-example list_tutorial ltrim_end_of_list >}}
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
> LTRIM bikes:repairs -3 -1
Expand All @@ -273,8 +286,7 @@ without any need to remember very old data.
Note: while [`LRANGE`]({{< relref "/commands/lrange" >}}) is technically an O(N) command, accessing small ranges
towards the head or the tail of the list is a constant time operation.

Blocking operations on lists
---
## Blocking operations on lists

Lists have a special feature that make them suitable to implement queues,
and in general as a building block for inter process communication systems:
Expand Down Expand Up @@ -304,6 +316,8 @@ timeout is reached.
This is an example of a [`BRPOP`]({{< relref "/commands/brpop" >}}) call we could use in the worker:

{{< clients-example list_tutorial brpop >}}
> DEL bikes:repairs
(integer) 1
> RPUSH bikes:repairs bike:1 bike:2
(integer) 2
> BRPOP bikes:repairs 1
Expand Down Expand Up @@ -366,6 +380,8 @@ Examples of rule 1:
However we can't perform operations against the wrong type if the key exists:

{{< clients-example list_tutorial rule_1.1 >}}
> DEL new_bikes
(integer) 1
> SET new_bikes bike:1
OK
> TYPE new_bikes
Expand All @@ -377,6 +393,8 @@ string
Example of rule 2:

{{< clients-example list_tutorial rule_2 >}}
> DEL bikes:repairs
(integer) 1
> LPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
> EXISTS bikes:repairs
Expand Down Expand Up @@ -407,8 +425,7 @@ Example of rule 3:

## Limits

The max length of a Redis list is 2^32 - 1 (4,294,967,295) elements.

The maximum length of a Redis list is 2^32 - 1 (4,294,967,295) elements.

## Performance

Expand Down
21 changes: 14 additions & 7 deletions local_examples/php/DtListTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// EXAMPLE: list_tutorial
// BINDER_ID php-dt-list
<?php

require 'vendor/autoload.php';
Expand Down Expand Up @@ -106,10 +107,11 @@ public function testDtList() {
$this->assertEquals('bike:2', $res12);
$this->assertEquals(['bike:1'], $res13);
$this->assertEquals(['bike:2'], $res14);
$r->del('bikes:repairs');
// REMOVE_END

// STEP_START lpush_rpush
$r->del('bikes:repairs');

$res15 = $r->rpush('bikes:repairs', 'bike:1');
echo $res15 . PHP_EOL;
// >>> 1
Expand All @@ -131,10 +133,11 @@ public function testDtList() {
$this->assertEquals(2, $res16);
$this->assertEquals(3, $res17);
$this->assertEquals(['bike:important_bike', 'bike:1', 'bike:2'], $res18);
$r->del('bikes:repairs');
// REMOVE_END

// STEP_START variadic
$r->del('bikes:repairs');

$res19 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3');
echo $res19 . PHP_EOL;
// >>> 3
Expand All @@ -157,10 +160,11 @@ public function testDtList() {
'bike:2',
'bike:3',
], $res21);
$r->del('bikes:repairs');
// REMOVE_END

// STEP_START lpop_rpop
$r->del('bikes:repairs');

$res22 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3');
echo $res22 . PHP_EOL;
// >>> 3
Expand Down Expand Up @@ -206,10 +210,11 @@ public function testDtList() {
$this->assertEquals(5, $res27);
$this->assertEquals('OK', $res28);
$this->assertEquals(['bike:1', 'bike:2', 'bike:3'], $res29);
$r->del('bikes:repairs');
// REMOVE_END

// STEP_START ltrim_end_of_list
$r->del('bikes:repairs');

$res27 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3', 'bike:4', 'bike:5');
echo $res27 . PHP_EOL;
// >>> 5
Expand All @@ -226,10 +231,11 @@ public function testDtList() {
$this->assertEquals(5, $res27);
$this->assertEquals('OK', $res28);
$this->assertEquals(['bike:3', 'bike:4', 'bike:5'], $res29);
$r->del('bikes:repairs');
// REMOVE_END

// STEP_START brpop
$r->del('bikes:repairs');

$res31 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2');
echo $res31 . PHP_EOL;
// >>> 2
Expand Down Expand Up @@ -267,10 +273,11 @@ public function testDtList() {
// REMOVE_START
$this->assertEquals(0, $res35);
$this->assertEquals(3, $res36);
$r->del('new_bikes');
// REMOVE_END

// STEP_START rule_1.1
$r->del('new_bikes');

$res37 = $r->set('new_bikes', 'bike:1');
echo $res37 . PHP_EOL;
// >>> True
Expand All @@ -297,10 +304,10 @@ public function testDtList() {
} catch (\Predis\Response\ServerException $e) {
$this->assertStringContainsString('wrong kind of value', strtolower($e->getMessage()));
}
$r->del('new_bikes');
// REMOVE_END

// STEP_START rule_2
$r->del('bikes:repairs');
$res36 = $r->lpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3');
echo $res36 . PHP_EOL;
// >>> 3
Expand Down
Loading