diff --git a/build/jupyterize/SPECIFICATION.md b/build/jupyterize/SPECIFICATION.md index 8560241cb3..a79cbd94a7 100644 --- a/build/jupyterize/SPECIFICATION.md +++ b/build/jupyterize/SPECIFICATION.md @@ -30,6 +30,10 @@ Pitfalls to avoid: - Save preamble before the first step and any trailing preamble at end - Apply unwrap patterns in listed order; for Java, remove `@Test` before method wrappers - Dedent after unwrapping when any unwrap patterns exist for the language +- **Boilerplate placement is not one-size-fits-all**: Go requires appending to first cell, not separate cell + - Check kernel requirements before deciding boilerplate strategy + - If kernel needs imports and boilerplate together, use Strategy 2 (append to first cell) + - Otherwise, use Strategy 1 (separate boilerplate cell) Add a new language (5 steps): 1) Copy the C# pattern set as a starting point @@ -1144,6 +1148,56 @@ Notes: - Keep patterns intentionally narrow and anchored to reduce false positives. - Java's `@Test` annotation pattern should come first to remove it before processing the method declaration +#### Go Configuration Example (Proposed) + +Go has a unique requirement: notebooks MUST have a `func main() {}` wrapper for gophernotes to execute code. Unlike C# and Java where wrappers are removed entirely, Go requires the wrapper to be injected as boilerplate. + +```json +{ + "go": { + "boilerplate": [ + "func main() {}" + ], + "unwrap_patterns": [ + { "type": "package_declaration", "pattern": "^package\\s+\\w+\\s*$", "end_pattern": "^package\\s+\\w+\\s*$", "keep_content": false, "description": "Remove package declaration" }, + { "type": "func_main_opening", "pattern": "^func\\s+main\\(\\)\\s*\\{\\s*$", "end_pattern": "^\\}\\s*$", "keep_content": false, "description": "Remove func main() wrapper" }, + { "type": "closing_braces", "pattern": "^\\s*\\}\\s*$", "end_pattern": "^\\s*\\}\\s*$", "keep_content": false, "description": "Remove orphaned closing braces" } + ] + } +} +``` + +**Key differences from C# and Java**: +- **Boilerplate is mandatory**: Go notebooks REQUIRE `func main() {}` wrapper (gophernotes requirement) +- **Package declaration removal**: Go source files have `package main` that must be removed +- **Wrapper removal**: The original `func main() { ... }` wrapper is removed, replaced by clean boilerplate +- **Pattern count**: Only 3 patterns needed (simpler than C# or Java) +- **No annotations or complex method signatures**: Go doesn't use annotations like Java or complex method signatures like C# + +**Boilerplate considerations**: +- **Go**: Requires `func main() {}` wrapper in first cell (appended to imports) + - This is NOT standard Go practice (normally you'd have code at package level) + - But gophernotes requires all code to be inside a function + - **Special handling**: For Go, boilerplate is APPENDED to the first cell (imports), not injected as a separate cell + - 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 + +**Pattern complexity comparison** (As Proposed): +- **C#**: 5 patterns (class/method wrappers + closing braces) +- **Java**: 8 patterns (annotations + class/method wrappers + static main + closing braces) +- **Go**: 3 patterns (package declaration + func main wrapper + closing braces) +- Go is simpler because it doesn't have annotations or complex class hierarchies + +**Critical insight for Go**: The wrapper removal strategy is different from C#/Java: +1. Remove `package main` declaration (test framework boilerplate) +2. Remove `func main() { ... }` wrapper (test framework wrapper) +3. Inject clean `func main() {}` as boilerplate (gophernotes requirement) +4. **Special handling**: Append boilerplate to first cell (imports), not as separate cell +5. Result: Notebook has imports and `func main() {}` in same first cell, with only example code inside + +**Implementation note**: The `create_cells()` function detects Go language and appends boilerplate to the first non-empty cell instead of creating a separate boilerplate cell. This ensures the notebook structure matches gophernotes expectations. + ### Runtime Order of Operations (within create_cells) 1) Load `lang_config = load_language_config(language)` @@ -1160,6 +1214,83 @@ Notes: This order ensures wrapper removal doesn’t leave code over-indented and avoids generating spurious empty cells. +### Boilerplate Placement Strategies (Lessons Learned) + +**Lesson Learned**: Not all languages should have boilerplate as a separate first cell. The placement strategy depends on kernel requirements and notebook structure expectations. + +#### Strategy 1: Separate Boilerplate Cell (Default) +**Used by**: C#, Java, and most languages + +**Characteristics**: +- Boilerplate is injected as a completely separate first cell +- Subsequent cells contain only example code +- Works well for languages where boilerplate (imports, setup) is naturally separate from example code + +**Example structure**: +``` +Cell 0: using StackExchange.Redis; (boilerplate) +Cell 1: var client = new Client(); (example code) +Cell 2: client.Set("key", "value"); (example code) +``` + +**Implementation**: +```python +if boilerplate and not append_boilerplate_to_first_cell: + boilerplate_cell = new_code_cell(source=boilerplate_code) + cells.append(boilerplate_cell) +``` + +#### Strategy 2: Append to First Cell (Go) +**Used by**: Go (gophernotes kernel) + +**Characteristics**: +- Boilerplate is appended to the first non-empty cell (imports) +- Ensures imports and `func main() {}` are in the same cell +- Required by gophernotes kernel expectations +- Boilerplate appears AFTER imports in the same cell + +**Example structure**: +``` +Cell 0: import ( + "fmt" + "github.com/redis/go-redis/v9" + ) + + func main() {} (boilerplate appended) +Cell 1: rdb := redis.NewClient(...) (example code) +Cell 2: rdb.Set(ctx, "key", "value") (example code) +``` + +**Implementation**: +```python +append_boilerplate_to_first_cell = language.lower() == 'go' + +# Skip separate boilerplate cell for Go +if boilerplate and not append_boilerplate_to_first_cell: + # ... create separate cell + +# Later, when processing first cell: +if append_boilerplate_to_first_cell and not first_cell_processed: + if boilerplate_code: + code = code + '\n\n' + boilerplate_code + first_cell_processed = True +``` + +**Why Go needs this**: +1. gophernotes kernel expects imports and `func main() {}` in the same cell +2. Separate cells would cause import errors when executing the boilerplate cell +3. Go's module system requires imports to be at the top of the file/function +4. The notebook structure must match Go's execution model + +**Decision Point for Future Languages**: +When adding a new language, ask: +- Does the kernel require boilerplate and code in the same cell? +- Are there import/dependency issues if boilerplate is separate? +- Does the language's execution model require a specific cell structure? + +If yes to any of these, use Strategy 2 (append to first cell). Otherwise, use Strategy 1 (separate cell). + + ### Testing Checklist (Language-Specific) #### General Tests (All Languages) @@ -1195,6 +1326,30 @@ This order ensures wrapper removal doesn’t leave code over-indented and avoids - Test files with `main()` methods (if present in examples) - Verify code inside wrappers is properly dedented +#### Go-Specific Tests +- Test with files from `local_examples/client-specific/go/` +- **Boilerplate placement** (Strategy 2: Append to First Cell): + - Verify first cell contains BOTH imports AND `func main() {}` (not separate cells) + - Verify imports appear BEFORE `func main() {}` in the same cell + - Verify no separate boilerplate cell exists + - Verify boilerplate is appended with blank line separator (`\n\n`) +- **Wrapper removal**: + - Verify `package main` declaration is removed + - Verify `func main() { ... }` wrapper is removed from source + - Verify no orphaned closing brace cells exist + - Verify all code inside wrapper is properly dedented +- **Code preservation**: + - Verify import statements are preserved in STEP blocks + - Verify all STEP blocks are preserved as separate cells + - Verify actual example code is intact and executable +- **Notebook structure**: + - Verify kernel is set to `gophernotes` + - Verify cell count matches expected (imports + N steps) + - Verify step metadata is preserved on each cell +- **Real-world testing**: + - Test with `landing_examples.go` (real repository file) + - Verify generated notebook is executable in gophernotes kernel + ### Edge Cases and Gotchas #### General Unwrapping Gotchas @@ -1221,6 +1376,56 @@ This order ensures wrapper removal doesn’t leave code over-indented and avoids - **Empty lines after class declaration**: Java style often has empty line after class opening brace - The unwrapping logic should handle this naturally by removing the class line and dedenting +#### Go-Specific Gotchas + +**Critical Requirement**: Go Jupyter notebooks using the gophernotes kernel MUST have all code wrapped in a `func main() {}` block. This is NOT standard Go practice, but it IS required for gophernotes to execute code. + +- **`func main() {}` wrapper is mandatory**: Unlike C# and Java where wrappers are removed entirely, Go requires the wrapper to be preserved in the notebook + - Solution: Inject `func main() {}` as boilerplate in the first cell + - Remove the original `func main() { ... }` wrapper from the source file using unwrap patterns + - This ensures the notebook has a clean `func main() {}` wrapper without the test framework code inside + +- **Package declaration must be removed**: Go source files start with `package main` (or other package names) + - This is test framework boilerplate and should be removed + - Pattern: `^package\\s+\\w+\\s*$` (single-line pattern) + - The boilerplate `func main() {}` replaces the need for package context + +- **Wrapper structure in source files**: Go test files have: + - Line 1-2: `// EXAMPLE:` and `// BINDER_ID` markers + - Line 3: `package main` declaration + - Lines 4+: Import statements (typically in STEP blocks) + - Lines N: `func main() { ... }` wrapper containing all example code + - Last line: Closing `}` for the main function + +- **Unwrap pattern order matters**: Must remove `package` declaration BEFORE removing `func main()` wrapper + - If `func main()` is removed first, the package line becomes orphaned + - Recommended order: `package_declaration` → `func_main_opening` + +- **Closing brace handling**: After removing `func main() { ... }` wrapper, the closing `}` will be removed by the generic closing brace pattern + - Ensure the closing brace pattern is included in unwrap_patterns + - The boilerplate `func main() {}` provides the wrapper, so orphaned closing braces should be filtered out + +- **Indentation inside func main()**: Code inside `func main() { ... }` is typically indented with tabs + - After unwrapping, dedent all cells to remove the extra indentation + - Go uses tabs by convention, so dedent should handle both tabs and spaces + +- **Boilerplate placement is special for Go** (Strategy 2: Append to First Cell): + - Unlike C# and Java where boilerplate is a separate first cell, Go appends boilerplate to the first cell + - This is because gophernotes expects imports and `func main() {}` in the same cell + - If boilerplate were a separate cell, executing it would fail (imports not available) + - Implementation: Detect Go language and append boilerplate to first non-empty cell instead of creating separate cell + - Result: First cell contains imports + blank line + `func main() {}` + - This is a critical difference from other languages and must be tested explicitly + +- **Real-world example**: `local_examples/client-specific/go/landing_examples.go` + - Source file has `func main() { ... }` wrapper (lines 15-105) + - Contains multiple STEP blocks inside the wrapper + - After conversion, should produce notebook with: + - Cell 1 (boilerplate): `func main() {}` + - Cell 2 (import step): Import statements + - Cell 3+ (other steps): Example code, properly dedented + - No orphaned closing brace cells + ### Recommended Implementation Strategy @@ -1251,10 +1456,28 @@ This order ensures wrapper removal doesn’t leave code over-indented and avoids **Critical lesson from Java implementation**: The specification initially estimated 6 patterns would be needed, but examining real files revealed that 8 patterns were required (added `static_main_single_line` and `static_main_opening` after seeing `main()` methods in real files). **Always examine real files BEFORE writing patterns.** -**Phase 4: Other Languages** (Lower Priority) +**Phase 4: Structural Unwrapping for Go** (High Priority) +1. **FIRST**: Examine real Go files to understand wrapper patterns: + - Look at `local_examples/client-specific/go/landing_examples.go` + - Note: Go files have `package main` declaration and `func main() { ... }` wrapper + - Unlike C#/Java, Go REQUIRES a `func main() {}` wrapper in notebooks (gophernotes requirement) +2. Add Go boilerplate: `func main() {}` as first cell +3. Add Go unwrap_patterns: + - `package_declaration`: Remove `package main` line + - `func_main_opening`: Remove `func main() { ... }` wrapper (multi-line pattern) + - Ensure closing braces are handled by existing pattern +4. Test with `local_examples/client-specific/go/landing_examples.go` +5. Verify: + - First cell contains `func main() {}` + - No `package main` declaration in notebook + - No orphaned closing brace cells + - All code properly dedented + - All STEP blocks preserved + +**Phase 5: Other Languages** (Lower Priority) 1. Add Node.js configuration (if needed) -2. Add other languages (Go, PHP, Rust) as needed -3. Most of these languages don't have the same structural wrapper issues as C#/Java +2. Add other languages (PHP, Rust) as needed +3. Most of these languages don't have the same structural wrapper issues as C#/Java/Go ### Configuration File Location diff --git a/build/jupyterize/jupyterize.py b/build/jupyterize/jupyterize.py index 0dad211d23..48360674ca 100755 --- a/build/jupyterize/jupyterize.py +++ b/build/jupyterize/jupyterize.py @@ -525,10 +525,16 @@ def create_cells(parsed_blocks, language): # Get language configuration lang_config = load_language_config(language) - # Add boilerplate cell if defined + # Get boilerplate if defined boilerplate = lang_config.get('boilerplate', []) - if boilerplate: - boilerplate_code = '\n'.join(boilerplate) + boilerplate_code = '\n'.join(boilerplate) if boilerplate else None + + # For Go, append boilerplate to first cell instead of creating separate cell + # This ensures imports and func main() {} are in the same cell + append_boilerplate_to_first_cell = language.lower() == 'go' + + # Add boilerplate cell if defined (except for Go, which appends to first cell) + if boilerplate and not append_boilerplate_to_first_cell: boilerplate_cell = new_code_cell(source=boilerplate_code) boilerplate_cell.metadata['cell_type'] = 'boilerplate' boilerplate_cell.metadata['language'] = language @@ -536,6 +542,7 @@ def create_cells(parsed_blocks, language): logging.info(f"Added boilerplate cell for {language} ({len(boilerplate)} lines)") # Process regular cells + first_cell_processed = False for i, block in enumerate(parsed_blocks): code = block['code'] @@ -568,11 +575,19 @@ def create_cells(parsed_blocks, language): logging.debug(f"Skipping cell {i} (contains only closing braces)") continue + # For Go: append boilerplate to first cell (imports) + if append_boilerplate_to_first_cell and not first_cell_processed: + if boilerplate_code: + code = code + '\n\n' + boilerplate_code + logging.info(f"Appended boilerplate to first cell for {language}") + first_cell_processed = True + # Create code cell cell = new_code_cell(source=code) - # Add step metadata if present - if block['step_name']: + # Add step metadata if present and enabled for this language + add_step_metadata = lang_config.get('add_step_metadata', True) # Default to True for backward compatibility + if block['step_name'] and add_step_metadata: cell.metadata['step'] = block['step_name'] logging.debug(f"Created cell {i} with step '{block['step_name']}'") else: diff --git a/build/jupyterize/jupyterize_config.json b/build/jupyterize/jupyterize_config.json index b307dbd5ce..653afe7c90 100644 --- a/build/jupyterize/jupyterize_config.json +++ b/build/jupyterize/jupyterize_config.json @@ -44,8 +44,32 @@ "unwrap_patterns": [] }, "go": { - "boilerplate": [], - "unwrap_patterns": [] + "boilerplate": [ + "func main() {}" + ], + "unwrap_patterns": [ + { + "type": "package_declaration", + "pattern": "^package\\s+\\w+\\s*$", + "end_pattern": "^package\\s+\\w+\\s*$", + "keep_content": false, + "description": "Remove package declaration" + }, + { + "type": "func_main_opening", + "pattern": "^func\\s+main\\(\\)\\s*\\{\\s*$", + "end_pattern": "^\\}\\s*$", + "keep_content": false, + "description": "Remove func main() wrapper" + }, + { + "type": "closing_braces", + "pattern": "^\\s*\\}\\s*$", + "end_pattern": "^\\s*\\}\\s*$", + "keep_content": false, + "description": "Remove orphaned closing braces" + } + ] }, "java": { "boilerplate": [], @@ -103,7 +127,8 @@ }, "php": { "boilerplate": [], - "unwrap_patterns": [] + "unwrap_patterns": [], + "add_step_metadata": false }, "rust": { "boilerplate": [], diff --git a/build/jupyterize/test_jupyterize.py b/build/jupyterize/test_jupyterize.py index 041e2270bb..ce7bbc12b9 100644 --- a/build/jupyterize/test_jupyterize.py +++ b/build/jupyterize/test_jupyterize.py @@ -515,6 +515,263 @@ def test_python_no_boilerplate(): os.unlink(output_file) +def test_go_boilerplate_injection(): + """Test Go boilerplate injection (func main() {} appended to first cell).""" + print("\nTesting Go boilerplate injection...") + + # Create test file with Go code + test_content = """// EXAMPLE: test_go +package main + +// STEP_START connect +import ( +\t"fmt" +\t"github.com/redis/go-redis/v9" +) +// STEP_END + +// STEP_START set_get +fmt.Println("Hello") +// STEP_END +""" + + test_file = None + output_file = None + + try: + # Create temporary test file + with tempfile.NamedTemporaryFile(mode='w', suffix='.go', delete=False) as f: + f.write(test_content) + test_file = f.name + + # Convert to notebook + output_file = tempfile.mktemp(suffix='.ipynb') + jupyterize(test_file, output_file) + + # Load and verify notebook + with open(output_file) as f: + nb = json.load(f) + + # Verify kernel is gophernotes + assert nb['metadata']['kernelspec']['name'] == 'gophernotes', \ + f"Kernel should be gophernotes, got {nb['metadata']['kernelspec']['name']}" + + # First cell should contain imports 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 'func main() {}' in first_cell_source, \ + f"First cell should contain 'func main() {{}}', got: {first_cell_source}" + + # Verify no package main declaration in any cell + all_code = '\n'.join([''.join(cell['source']) for cell in nb['cells']]) + assert 'package main' not in all_code, \ + "Should not contain 'package main' declaration" + + print("✓ Go boilerplate injection test passed") + + finally: + if test_file and os.path.exists(test_file): + os.unlink(test_file) + if output_file and os.path.exists(output_file): + os.unlink(output_file) + + +def test_go_unwrapping(): + """Test Go func main() wrapper removal and package declaration removal.""" + print("\nTesting Go unwrapping...") + + # Create test file with Go code that has func main() wrapper + test_content = """// EXAMPLE: test_go_unwrap +package main + +import ( +\t"fmt" +) + +func main() { +\t// STEP_START hello +\tfmt.Println("Hello, World!") +\t// STEP_END +} +""" + + test_file = None + output_file = None + + try: + # Create temporary test file + with tempfile.NamedTemporaryFile(mode='w', suffix='.go', delete=False) as f: + f.write(test_content) + test_file = f.name + + # Convert to notebook + output_file = tempfile.mktemp(suffix='.ipynb') + jupyterize(test_file, output_file) + + # Load and verify notebook + with open(output_file) as f: + nb = json.load(f) + + # Get all code + all_code = '\n'.join([''.join(cell['source']) for cell in nb['cells']]) + + # Verify package main is removed + assert 'package main' not in all_code, \ + "Should not contain 'package main' declaration" + + # Verify func main() wrapper is removed from source + # (but boilerplate should have it) + assert all_code.count('func main()') == 1, \ + f"Should have exactly one 'func main()' (in boilerplate), got: {all_code.count('func main()')}" + + # Verify no orphaned closing braces + for cell in nb['cells']: + cell_code = ''.join(cell['source']).strip() + if cell_code and all(c in '}\n\t ' for c in cell_code): + assert False, f"Found orphaned closing brace cell: {repr(cell_code)}" + + # Verify actual code is present and dedented + assert 'fmt.Println("Hello, World!")' in all_code, \ + "Should contain actual code" + + print("✓ Go unwrapping test passed") + + finally: + if test_file and os.path.exists(test_file): + os.unlink(test_file) + if output_file and os.path.exists(output_file): + os.unlink(output_file) + + +def test_go_real_file(): + """Test with real Go file (landing_examples.go).""" + print("\nTesting with real Go file (landing_examples.go)...") + + input_file = 'local_examples/client-specific/go/landing_examples.go' + output_file = None + + try: + # Skip test if file doesn't exist + if not os.path.exists(input_file): + print("⊘ Skipping real Go file test (file not found)") + return + + # Convert to notebook + output_file = tempfile.mktemp(suffix='.ipynb') + jupyterize(input_file, output_file) + + # Load and verify notebook + with open(output_file) as f: + nb = json.load(f) + + # Verify kernel is gophernotes + assert nb['metadata']['kernelspec']['name'] == 'gophernotes', \ + f"Kernel should be gophernotes, got {nb['metadata']['kernelspec']['name']}" + + # Verify first cell contains imports AND func main() {} + 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 'func main() {}' in first_cell_source, \ + f"First cell should contain 'func main() {{}}', got: {first_cell_source}" + + # Get all code + all_code = '\n'.join([''.join(cell['source']) for cell in nb['cells']]) + + # Verify no package main + assert 'package main' not in all_code, \ + "Should not contain 'package main' declaration" + + # Verify no orphaned closing braces + for cell in nb['cells']: + cell_code = ''.join(cell['source']).strip() + if cell_code == '}': + assert False, f"Found orphaned closing brace cell" + + # Verify import statements are preserved + assert 'import (' in all_code, \ + "Should contain import statements" + assert 'github.com/redis/go-redis' in all_code, \ + "Should contain redis import" + + # Verify actual code is present + assert 'redis.NewClient' in all_code or 'rdb :=' in all_code, \ + "Should contain actual code" + + # Should have multiple cells (one per step) + assert len(nb['cells']) >= 4, \ + f"Should have at least 4 cells, got {len(nb['cells'])}" + + print("✓ Real Go file test passed") + + finally: + if output_file and os.path.exists(output_file): + os.unlink(output_file) + + +def test_php_no_step_metadata(): + """Test that PHP notebooks don't include step metadata.""" + print("\nTesting PHP (no step metadata)...") + + # Create test file + test_content = """// EXAMPLE: test +// BINDER_ID php-test +// STEP_START connect +set('foo', 'bar'); +$r->get('foo'); +// STEP_END +""" + + with tempfile.NamedTemporaryFile(mode='w', suffix='.php', delete=False) as f: + f.write(test_content) + test_file = f.name + + try: + # Convert + output_file = test_file.replace('.php', '.ipynb') + result = jupyterize(test_file, output_file, verbose=False) + + # Load and validate notebook + with open(output_file) as f: + nb = json.load(f) + + # Verify kernel is php + assert nb['metadata']['kernelspec']['name'] == 'php', \ + f"Kernel should be php, got {nb['metadata']['kernelspec']['name']}" + + # Verify no step metadata is added + for i, cell in enumerate(nb['cells']): + assert 'step' not in cell['metadata'], \ + f"Cell {i} should not have step metadata, but has: {cell['metadata'].get('step')}" + + # Verify we have 2 cells (one per step) + assert len(nb['cells']) == 2, \ + f"Should have 2 cells, got {len(nb['cells'])}" + + # Verify content is present + all_code = ''.join(''.join(cell['source']) for cell in nb['cells']) + assert '$r = new Client()' in all_code, "Should contain connection code" + assert '$r->set' in all_code, "Should contain set code" + assert '$r->get' in all_code, "Should contain get code" + + print("✓ PHP (no step metadata) test passed") + + finally: + if os.path.exists(test_file): + os.unlink(test_file) + if output_file and os.path.exists(output_file): + os.unlink(output_file) + + def main(): """Run all tests.""" print("=" * 60) @@ -547,6 +804,14 @@ def main(): test_java_static_main_unwrapping() test_java_real_file() + # Language-specific feature tests (Go) + test_go_boilerplate_injection() + test_go_unwrapping() + test_go_real_file() + + # Language-specific feature tests (PHP) + test_php_no_step_metadata() + # Regression tests test_csharp_for_loop_braces() diff --git a/local_examples/php/DtStringTest.php b/local_examples/php/DtStringTest.php index 6d0fc02012..6ea1420a7e 100644 --- a/local_examples/php/DtStringTest.php +++ b/local_examples/php/DtStringTest.php @@ -1,4 +1,5 @@ // EXAMPLE: set_tutorial +// BINDER_ID php-dt-string res6 = jedis.mget("bike:1", "bike:2", "bike:3"); + System.out.println(res6); // [Deimos, Ares, Vanth] + // STEP_END + + // REMOVE_START + assertEquals("OK", res5); + List expected = new ArrayList<>(Arrays.asList("Deimos", "Ares", "Vanth")); + assertEquals(expected, res6); + // REMOVE_END + + // STEP_START incr + jedis.set("total_crashes", "0"); + Long res7 = jedis.incr("total_crashes"); + System.out.println(res7); // 1 + Long res8 = jedis.incrBy("total_crashes", 10); + System.out.println(res8); // 11 + // STEP_END + + // REMOVE_START + assertEquals(1L, res7.longValue()); + assertEquals(11L, res8.longValue()); + // REMOVE_END + } + } +} diff --git a/local_examples/tmp/datatypes/strings/StringSnippets.cs b/local_examples/tmp/datatypes/strings/StringSnippets.cs new file mode 100644 index 0000000000..2e3d79ab7a --- /dev/null +++ b/local_examples/tmp/datatypes/strings/StringSnippets.cs @@ -0,0 +1,91 @@ +// EXAMPLE: set_tutorial +// BINDER_ID netsync-dt-string +// HIDE_START + +//REMOVE_START + +using NRedisStack.Tests; +using StackExchange.Redis; + +namespace Doc; +[Collection("DocsTests")] +//REMOVE_END +public class StringSnippets +// REMOVE_START +: AbstractNRedisStackTest, IDisposable +// REMOVE_END +{ + // REMOVE_START + public StringSnippets(EndpointsFixture fixture) : base(fixture) { } + + [SkippableFact] + // REMOVE_END + public void Run() + { + //REMOVE_START + // This is needed because we're constructing ConfigurationOptions in the test before calling GetConnection + SkipIfTargetConnectionDoesNotExist(EndpointsFixture.Env.Standalone); + var _ = GetCleanDatabase(EndpointsFixture.Env.Standalone); + //REMOVE_END + var muxer = ConnectionMultiplexer.Connect("localhost:6379"); + var db = muxer.GetDatabase(); + + //HIDE_END + + //REMOVE_START + db.KeyDelete(["bike:1", "bike:2", "bike:3", "total_crashes"]); + //REMOVE_END + + // STEP_START set_get + var res1 = db.StringSet("bike:1", "Deimos"); + Console.WriteLine(res1); // true + var res2 = db.StringGet("bike:1"); + Console.WriteLine(res2); // Deimos + // STEP_END + + //REMOVE_START + Assert.True(res1); + Assert.Equal("Deimos", res2); + //REMOVE_END + + //STEP_START setnx_xx + var res3 = db.StringSet("bike:1", "bike", when: When.NotExists); + Console.WriteLine(res3); // false + Console.WriteLine(db.StringGet("bike:1")); + var res4 = db.StringSet("bike:1", "bike", when: When.Exists); + Console.WriteLine(res4); // true + //STEP_END + + //REMOVE_START + Assert.False(res3); + Assert.True(res4); + //REMOVE_END + + //STEP_START mset + var res5 = db.StringSet([ + new ("bike:1", "Deimos"), new("bike:2", "Ares"), new("bike:3", "Vanth") + ]); + Console.WriteLine(res5); + var res6 = db.StringGet(["bike:1", "bike:2", "bike:3"]); + Console.WriteLine(string.Join(", ", res6)); + //STEP_END + + //REMOVE_START + Assert.True(res5); + Assert.Equal(new[] { "Deimos", "Ares", "Vanth" }, res6.Select(x => x.ToString()).ToArray()); + //REMOVE_END + + //STEP_START incr + db.StringSet("total_crashes", 0); + var res7 = db.StringIncrement("total_crashes"); + Console.WriteLine(res7); // 1 + var res8 = db.StringIncrement("total_crashes", 10); + Console.WriteLine(res8); + //STEP_END + + //REMOVE_START + Assert.Equal(1, res7); + Assert.Equal(11, res8); + //REMOVE_END + } +} \ No newline at end of file diff --git a/local_examples/tmp/datatypes/strings/dt-string.js b/local_examples/tmp/datatypes/strings/dt-string.js new file mode 100644 index 0000000000..3d9ab300ec --- /dev/null +++ b/local_examples/tmp/datatypes/strings/dt-string.js @@ -0,0 +1,69 @@ +// EXAMPLE: set_tutorial +// BINDER_ID nodejs-dt-string +// HIDE_START +import assert from 'assert'; +import { createClient } from 'redis'; + +const client = createClient(); +await client.connect(); +// HIDE_END +// REMOVE_START +await client.flushDb(); +// REMOVE_END + +// STEP_START set_get +const res1 = await client.set("bike:1", "Deimos"); +console.log(res1); // OK +const res2 = await client.get("bike:1"); +console.log(res2); // Deimos +// STEP_END + +// REMOVE_START +assert.equal(res1, 'OK'); +assert.equal(res2, 'Deimos'); +// REMOVE_END + +// STEP_START setnx_xx +const res3 = await client.set("bike:1", "bike", {'NX': true}); +console.log(res3); // null +console.log(await client.get("bike:1")); // Deimos +const res4 = await client.set("bike:1", "bike", {'XX': true}); +console.log(res4); // OK +// STEP_END + +// REMOVE_START +assert.equal(res3, null); +assert.equal(res4, 'OK'); +// REMOVE_END + +// STEP_START mset +const res5 = await client.mSet([ + ["bike:1", "Deimos"], + ["bike:2", "Ares"], + ["bike:3", "Vanth"] +]); + +console.log(res5); // OK +const res6 = await client.mGet(["bike:1", "bike:2", "bike:3"]); +console.log(res6); // ['Deimos', 'Ares', 'Vanth'] +// STEP_END + +// REMOVE_START +assert.equal(res5, 'OK'); +assert.deepEqual(res6, ["Deimos", "Ares", "Vanth"]); +// REMOVE_END + +// STEP_START incr +await client.set("total_crashes", 0); +const res7 = await client.incr("total_crashes"); +console.log(res7); // 1 +const res8 = await client.incrBy("total_crashes", 10); +console.log(res8); // 11 +// STEP_END + +// REMOVE_START +assert.equal(res7, 1); +assert.equal(res8, 11); + +await client.close(); +// REMOVE_END diff --git a/local_examples/tmp/datatypes/strings/dt_string.py b/local_examples/tmp/datatypes/strings/dt_string.py new file mode 100644 index 0000000000..7ca21fda5b --- /dev/null +++ b/local_examples/tmp/datatypes/strings/dt_string.py @@ -0,0 +1,62 @@ +# EXAMPLE: set_tutorial +# BINDER_ID python-dt-string +# HIDE_START +""" +Code samples for String doc pages: + https://redis.io/docs/latest/develop/data-types/strings/ +""" + +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# STEP_START set_get +res1 = r.set("bike:1", "Deimos") +print(res1) # True +res2 = r.get("bike:1") +print(res2) # Deimos +# STEP_END + +# REMOVE_START +assert res1 +assert res2 == "Deimos" +# REMOVE_END + +# STEP_START setnx_xx +res3 = r.set("bike:1", "bike", nx=True) +print(res3) # None +print(r.get("bike:1")) # Deimos +res4 = r.set("bike:1", "bike", xx=True) +print(res4) # True +# STEP_END + +# REMOVE_START +assert res3 is None +assert res4 +# REMOVE_END + +# STEP_START mset +res5 = r.mset({"bike:1": "Deimos", "bike:2": "Ares", "bike:3": "Vanth"}) +print(res5) # True +res6 = r.mget(["bike:1", "bike:2", "bike:3"]) +print(res6) # ['Deimos', 'Ares', 'Vanth'] +# STEP_END + +# REMOVE_START +assert res5 +assert res6 == ["Deimos", "Ares", "Vanth"] +# REMOVE_END + +# STEP_START incr +r.set("total_crashes", 0) +res7 = r.incr("total_crashes") +print(res7) # 1 +res8 = r.incrby("total_crashes", 10) +print(res8) # 11 +# STEP_END + +# REMOVE_START +assert res7 == 1 +assert res8 == 11 +# REMOVE_END diff --git a/local_examples/tmp/datatypes/strings/string_example_test.go b/local_examples/tmp/datatypes/strings/string_example_test.go new file mode 100644 index 0000000000..b3765478a3 --- /dev/null +++ b/local_examples/tmp/datatypes/strings/string_example_test.go @@ -0,0 +1,182 @@ +// EXAMPLE: set_tutorial +// BINDER_ID go-dt-string +// HIDE_START +package example_commands_test + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9" +) + +// HIDE_END +func ExampleClient_set_get() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + // start with fresh database + rdb.FlushDB(ctx) + rdb.Del(ctx, "bike:1") + // REMOVE_END + + // STEP_START set_get + res1, err := rdb.Set(ctx, "bike:1", "Deimos", 0).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.Get(ctx, "bike:1").Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> Deimos + // STEP_END + + // Output: + // OK + // Deimos +} + +func ExampleClient_setnx_xx() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + // start with fresh database + rdb.FlushDB(ctx) + rdb.Set(ctx, "bike:1", "Deimos", 0) + // REMOVE_END + + // STEP_START setnx_xx + res3, err := rdb.SetNX(ctx, "bike:1", "bike", 0).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> false + + res4, err := rdb.Get(ctx, "bike:1").Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> Deimos + + res5, err := rdb.SetXX(ctx, "bike:1", "bike", 0).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res5) // >>> OK + // STEP_END + + // Output: + // false + // Deimos + // true +} + +func ExampleClient_mset() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + // start with fresh database + rdb.FlushDB(ctx) + rdb.Del(ctx, "bike:1", "bike:2", "bike:3") + // REMOVE_END + + // STEP_START mset + res6, err := rdb.MSet(ctx, "bike:1", "Deimos", "bike:2", "Ares", "bike:3", "Vanth").Result() + + if err != nil { + panic(err) + } + + fmt.Println(res6) // >>> OK + + res7, err := rdb.MGet(ctx, "bike:1", "bike:2", "bike:3").Result() + + if err != nil { + panic(err) + } + + fmt.Println(res7) // >>> [Deimos Ares Vanth] + // STEP_END + + // Output: + // OK + // [Deimos Ares Vanth] +} + +func ExampleClient_incr() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + // start with fresh database + rdb.FlushDB(ctx) + rdb.Del(ctx, "total_crashes") + // REMOVE_END + + // STEP_START incr + res8, err := rdb.Set(ctx, "total_crashes", "0", 0).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res8) // >>> OK + + res9, err := rdb.Incr(ctx, "total_crashes").Result() + + if err != nil { + panic(err) + } + + fmt.Println(res9) // >>> 1 + + res10, err := rdb.IncrBy(ctx, "total_crashes", 10).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res10) // >>> 11 + // STEP_END + + // Output: + // OK + // 1 + // 11 +}