Skip to content

Commit 9da2221

Browse files
committed
Merge branch 'main' into preview-rc-security-reorg
2 parents a3be24c + 94eba7d commit 9da2221

File tree

262 files changed

+18138
-1349
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

262 files changed

+18138
-1349
lines changed

assets/css/index.css

Lines changed: 526 additions & 20 deletions
Large diffs are not rendered by default.

build/add_cmds.py

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import json
4+
import logging
5+
import os
6+
import sys
7+
8+
from components.syntax import Command
9+
from components.markdown import Markdown
10+
11+
12+
def command_filename(name: str) -> str:
13+
"""Convert command name to filename format."""
14+
return name.lower().replace(' ', '-')
15+
16+
17+
def parse_args() -> argparse.Namespace:
18+
parser = argparse.ArgumentParser(description='Creates new Redis command pages from JSON input')
19+
parser.add_argument('json_file', type=str,
20+
help='Path to JSON file containing command definitions')
21+
parser.add_argument('--loglevel', type=str,
22+
default='INFO',
23+
help='Python logging level (overwrites LOGLEVEL env var)')
24+
return parser.parse_args()
25+
26+
27+
def validate_json_structure(data: dict, filename: str) -> None:
28+
"""Validate that the JSON has the expected structure for Redis commands."""
29+
if not isinstance(data, dict):
30+
raise ValueError(f"JSON file {filename} must contain a dictionary at root level")
31+
32+
for command_name, command_data in data.items():
33+
if not isinstance(command_data, dict):
34+
raise ValueError(f"Command '{command_name}' must be a dictionary")
35+
36+
# Check for required fields
37+
required_fields = ['summary', 'since', 'group']
38+
for field in required_fields:
39+
if field not in command_data:
40+
logging.warning(f"Command '{command_name}' missing recommended field: {field}")
41+
42+
# Validate arguments structure if present
43+
if 'arguments' in command_data:
44+
if not isinstance(command_data['arguments'], list):
45+
raise ValueError(f"Command '{command_name}' arguments must be a list")
46+
47+
48+
def load_and_validate_json(filepath: str) -> dict:
49+
"""Load and validate the JSON file containing command definitions."""
50+
if not os.path.exists(filepath):
51+
raise FileNotFoundError(f"JSON file not found: {filepath}")
52+
53+
try:
54+
with open(filepath, 'r') as f:
55+
data = json.load(f)
56+
except json.JSONDecodeError as e:
57+
raise ValueError(f"Invalid JSON in file {filepath}: {e}")
58+
59+
validate_json_structure(data, filepath)
60+
return data
61+
62+
63+
def add_standard_categories(fm_data: dict) -> None:
64+
"""Add the standard categories from create.sh script."""
65+
standard_categories = [
66+
'docs', 'develop', 'stack', 'oss', 'rs', 'rc', 'oss', 'kubernetes', 'clients'
67+
]
68+
fm_data['categories'] = standard_categories
69+
70+
71+
def get_full_command_name(command_name: str, command_data: dict) -> str:
72+
"""Get the full command name, handling container commands."""
73+
container = command_data.get('container')
74+
if container:
75+
return f"{container} {command_name}"
76+
return command_name
77+
78+
79+
def generate_command_frontmatter(command_name: str, command_data: dict, all_commands: dict) -> dict:
80+
"""Generate complete Hugo frontmatter for a command using existing build infrastructure."""
81+
# Get the full command name (handles container commands)
82+
full_command_name = get_full_command_name(command_name, command_data)
83+
84+
# Create Command object to generate syntax using the full command name
85+
c = Command(full_command_name, command_data)
86+
87+
# Start with the command data
88+
fm_data = command_data.copy()
89+
90+
# Add required Hugo frontmatter fields
91+
fm_data.update({
92+
'title': full_command_name,
93+
'linkTitle': full_command_name,
94+
'description': command_data.get('summary'),
95+
'syntax_str': str(c),
96+
'syntax_fmt': c.syntax(),
97+
'hidden': False # Default to not hidden
98+
})
99+
100+
# Add the standard categories from create.sh
101+
add_standard_categories(fm_data)
102+
103+
return fm_data
104+
105+
106+
def generate_argument_sections(command_data: dict) -> str:
107+
"""Generate placeholder sections for Required arguments and Optional arguments."""
108+
content = ""
109+
110+
arguments = command_data.get('arguments', [])
111+
if not arguments:
112+
return content
113+
114+
required_args = []
115+
optional_args = []
116+
117+
# Categorize arguments
118+
for arg in arguments:
119+
if arg.get('optional', False):
120+
optional_args.append(arg)
121+
else:
122+
required_args.append(arg)
123+
124+
# Generate Required arguments section
125+
if required_args:
126+
content += "## Required arguments\n\n"
127+
for arg in required_args:
128+
arg_name = arg.get('name', 'unknown')
129+
arg_type = arg.get('type', 'unknown')
130+
display_text = arg.get('display_text', arg_name)
131+
132+
content += f"<details open><summary><code>{display_text}</code></summary>\n\n"
133+
content += f"TODO: Add description for {arg_name} ({arg_type})\n\n"
134+
content += "</details>\n\n"
135+
136+
# Generate Optional arguments section
137+
if optional_args:
138+
content += "## Optional arguments\n\n"
139+
for arg in optional_args:
140+
arg_name = arg.get('name', 'unknown')
141+
arg_type = arg.get('type', 'unknown')
142+
display_text = arg.get('display_text', arg_name)
143+
token = arg.get('token', '')
144+
145+
content += f"<details open><summary><code>{token if token else display_text}</code></summary>\n\n"
146+
content += f"TODO: Add description for {arg_name} ({arg_type})\n\n"
147+
content += "</details>\n\n"
148+
149+
return content
150+
151+
152+
def generate_return_section() -> str:
153+
"""Generate placeholder Return information section."""
154+
return '''## Return information
155+
156+
{{< multitabs id="return-info"
157+
tab1="RESP2"
158+
tab2="RESP3" >}}
159+
160+
TODO: Add RESP2 return information
161+
162+
-tab-sep-
163+
164+
TODO: Add RESP3 return information
165+
166+
{{< /multitabs >}}
167+
168+
'''
169+
170+
171+
def generate_complete_markdown_content(command_name: str, command_data: dict) -> str:
172+
"""Generate the complete markdown content for a command page."""
173+
content = ""
174+
175+
# Add command summary as the main description
176+
summary = command_data.get('summary', f'TODO: Add summary for {command_name}')
177+
content += f"{summary}\n\n"
178+
179+
# Add argument sections
180+
content += generate_argument_sections(command_data)
181+
182+
# Add return information section
183+
content += generate_return_section()
184+
185+
return content
186+
187+
188+
def create_command_file(command_name: str, command_data: dict, all_commands: dict) -> str:
189+
"""Create a complete command markdown file with frontmatter and content."""
190+
# Get the full command name (handles container commands)
191+
full_command_name = get_full_command_name(command_name, command_data)
192+
193+
# Generate the file path using the full command name
194+
filename = command_filename(full_command_name)
195+
filepath = f'content/commands/{filename}.md'
196+
197+
# Ensure the directory exists
198+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
199+
200+
# Check if file already exists
201+
if os.path.exists(filepath):
202+
logging.warning(f"File {filepath} already exists, skipping...")
203+
return filepath
204+
205+
# Generate frontmatter
206+
frontmatter_data = generate_command_frontmatter(command_name, command_data, all_commands)
207+
208+
# Generate content
209+
content = generate_complete_markdown_content(command_name, command_data)
210+
211+
# Create markdown object and set data
212+
md = Markdown(filepath)
213+
md.fm_data = frontmatter_data
214+
md.payload = content
215+
216+
# Write the file
217+
md.persist()
218+
219+
logging.info(f"Created command file: {filepath}")
220+
return filepath
221+
222+
223+
if __name__ == '__main__':
224+
args = parse_args()
225+
226+
# Configure logging BEFORE creating objects
227+
log_level = getattr(logging, args.loglevel.upper())
228+
logging.basicConfig(
229+
level=log_level,
230+
format='%(message)s %(filename)s:%(lineno)d - %(funcName)s',
231+
force=True # Force reconfiguration in case logging was already configured
232+
)
233+
234+
try:
235+
# Load and validate JSON data
236+
commands_data = load_and_validate_json(args.json_file)
237+
logging.info(f"Loaded {len(commands_data)} commands from {args.json_file}")
238+
239+
# Process each command and generate markdown files
240+
created_files = []
241+
for command_name in commands_data:
242+
try:
243+
logging.info(f"Processing command: {command_name}")
244+
filepath = create_command_file(command_name, commands_data[command_name], commands_data)
245+
created_files.append(filepath)
246+
except Exception as e:
247+
logging.error(f"Failed to create file for command '{command_name}': {e}")
248+
# Continue processing other commands
249+
continue
250+
251+
# Summary
252+
logging.info(f"Successfully created {len(created_files)} command files:")
253+
for filepath in created_files:
254+
logging.info(f" - {filepath}")
255+
256+
except (FileNotFoundError, ValueError) as e:
257+
logging.error(f"Error: {e}")
258+
sys.exit(1)
259+
except Exception as e:
260+
logging.error(f"Unexpected error: {e}")
261+
sys.exit(1)

build/components/example.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
'java-sync': '@Test',
1515
'java-async': '@Test',
1616
'java-reactive': '@Test',
17-
'c#': r'\[Fact]|\[SkipIfRedis\(.*\)]'
17+
'c#': r'\[Fact]|\[SkipIfRedis\(.*\)]',
18+
'c#-sync': r'\[Fact]|\[SkipIfRedis\(.*\)]',
19+
'c#-async': r'\[Fact]|\[SkipIfRedis\(.*\)]',
20+
'rust': r'#\[test]|#\[cfg\(test\)]|#\[tokio::test]'
1821
}
1922
PREFIXES = {
2023
'python': '#',
@@ -25,8 +28,13 @@
2528
'java-reactive': '//',
2629
'go': '//',
2730
'c#': '//',
31+
'c#-sync': '//',
32+
'c#-async': '//',
2833
'redisvl': '#',
29-
'php': '//'
34+
'php': '//',
35+
'rust': '//',
36+
'rust-sync': '//',
37+
'rust-async': '//'
3038
}
3139

3240

build/local_examples.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@
2525
'.go': 'go',
2626
'.cs': 'c#',
2727
'.java': 'java',
28-
'.php': 'php'
28+
'.php': 'php',
29+
'.rs': 'rust'
2930
}
3031

3132
# Language to client name mapping (from config.toml clientsExamples)
3233
LANGUAGE_TO_CLIENT = {
3334
'python': 'Python',
3435
'node.js': 'Node.js',
3536
'go': 'Go',
36-
'c#': 'C#',
37+
'c#': 'C#-Sync',
3738
'java': 'Java-Sync', # Default to sync, could be overridden
3839
'php': 'PHP',
39-
'redisvl': 'RedisVL'
40+
'redisvl': 'RedisVL',
41+
'rust': 'Rust-Sync'
4042
}
4143

4244

@@ -65,6 +67,16 @@ def get_client_name_from_language_and_path(language: str, path: str) -> str:
6567
return 'Java-Async'
6668
if 'lettuce-reactive' in path:
6769
return 'Java-Reactive'
70+
if language == 'rust':
71+
if 'rust-async' in path:
72+
return 'Rust-Async'
73+
if 'rust-sync' in path:
74+
return 'Rust-Sync'
75+
if language == 'c#':
76+
if 'async' in path:
77+
return 'C#-Async'
78+
if 'sync' in path:
79+
return 'C#-Sync'
6880
# Default behavior for all languages (and Java fallback)
6981
return get_client_name_from_language(language)
7082

config.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ tagManagerId = "GTM-TKZ6J9R"
4545
gitHubRepo = "https://github.com/redis/docs"
4646

4747
# Display and sort order for client examples
48-
clientsExamples = ["Python", "Node.js", "Java-Sync", "Java-Async", "Java-Reactive", "Go", "C#", "RedisVL", "PHP"]
48+
clientsExamples = ["Python", "Node.js", "Java-Sync", "Java-Async", "Java-Reactive", "Go", "C#-Sync", "C#-Async", "RedisVL", "PHP", "Rust-Sync", "Rust-Async"]
4949
searchService = "/convai/api/search-service"
50-
ratingsService = "/docusight/api/rate"
50+
ratingsService = "/docusight/api/rate/docs"
5151

5252
# RDI params
5353
rdi_rlec_min_version = "6.2.18"
5454
rdi_redis_gears_version = "1.2.6"
5555
rdi_debezium_server_version = "2.3.0.Final"
5656
rdi_db_types = "cassandra|mysql|oracle|postgresql|sqlserver"
5757
rdi_cli_latest = "latest"
58-
rdi_current_version = "1.14.0"
58+
rdi_current_version = "1.14.1"
5959

6060
[params.clientsConfig]
6161
"Python"={quickstartSlug="redis-py"}
@@ -64,9 +64,12 @@ rdi_current_version = "1.14.0"
6464
"Java-async"={quickstartSlug="lettuce"}
6565
"Java-reactive"={quickstartSlug="lettuce"}
6666
"Go"={quickstartSlug="go"}
67-
"C#"={quickstartSlug="dotnet"}
67+
"C#-Sync"={quickstartSlug="dotnet"}
68+
"C#-Async"={quickstartSlug="dotnet"}
6869
"RedisVL"={quickstartSlug="redis-vl"}
6970
"PHP"={quickstartSlug="php"}
71+
"Rust-sync"={quickstartSlug="rust"}
72+
"Rust-async"={quickstartSlug="rust"}
7073

7174
# Markup
7275
[markup]

content/commands/acl-log.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ title: ACL LOG
4747
---
4848
The command shows a list of recent ACL security events:
4949

50-
1. Failures to authenticate their connections with [`AUTH`]({{< relref "/commands/auth" >}}) or [`HELLO`]({{< relref "/commands/hello" >}}).
51-
2. Commands denied because against the current ACL rules.
52-
3. Commands denied because accessing keys not allowed in the current ACL rules.
50+
1. Failed authentications with [`AUTH`]({{< relref "/commands/auth" >}}) or [`HELLO`]({{< relref "/commands/hello" >}}) (reason = auth)
51+
2. Commands violating the current ACL rules
52+
- Disallowed commands (reason = command).
53+
- Disallowed keys (reason = key).
54+
- Disallowed pub/sub channel (reason = channel).
5355

5456
The optional argument specifies how many entries to show. By default
5557
up to ten failures are returned. The special [`RESET`]({{< relref "/commands/reset" >}}) argument clears the log.

content/commands/exists.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ The user should be aware that if the same existing key is mentioned in the argum
5858

5959
## Examples
6060

61+
{{< clients-example set="cmds_generic" step="exists" >}}
62+
SET key1 "Hello"
63+
EXISTS key1
64+
EXISTS nosuchkey
65+
SET key2 "World"
66+
EXISTS key1 key2 nosuchkey
67+
{{< /clients-example >}}
68+
69+
Give these commands a try in the interactive console:
70+
6171
{{% redis-cli %}}
6272
SET key1 "Hello"
6373
EXISTS key1

0 commit comments

Comments
 (0)