Skip to content

Commit 9fb490b

Browse files
authored
Merge pull request #4236 from Blargian/code_block_imports
Allow code blocks to import code from files or URLs
2 parents 7e652cd + b0b9528 commit 9fb490b

File tree

6 files changed

+273
-5
lines changed

6 files changed

+273
-5
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ Please assign any pull request (PR) against an issue; this helps the docs team t
134134
135135
Check out the GitHub docs for a refresher on [how to create a pull request](https://docs.github.com/en/desktop/working-with-your-remote-repository-on-github-or-github-enterprise/creating-an-issue-or-pull-request-from-github-desktop).
136136
137-
### Style guidelines
137+
### Style and contribution guidelines
138138
139-
For documentation style guidelines, see ["Style guide"](/contribute/style-guide.md).
139+
For documentation style guidelines, see ["Style guide"](/contribute/style-guide.md).
140140
141141
To check spelling and markdown is correct locally run:
142142
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
receivers:
2+
filelog:
3+
include:
4+
- /opt/data/logs/access-unstructured.log
5+
start_at: beginning
6+
operators:
7+
- type: regex_parser
8+
regex: '^(?P<ip>[\d.]+)\s+-\s+-\s+\[(?P<timestamp>[^\]]+)\]\s+"(?P<method>[A-Z]+)\s+(?P<url>[^\s]+)\s+HTTP/[^\s]+"\s+(?P<status>\d+)\s+(?P<size>\d+)\s+"(?P<referrer>[^"]*)"\s+"(?P<user_agent>[^"]*)"'
9+
timestamp:
10+
parse_from: attributes.timestamp
11+
layout: '%d/%b/%Y:%H:%M:%S %z'
12+
#22/Jan/2019:03:56:14 +0330
13+
processors:
14+
batch:
15+
timeout: 1s
16+
send_batch_size: 100
17+
memory_limiter:
18+
check_interval: 1s
19+
limit_mib: 2048
20+
spike_limit_mib: 256
21+
exporters:
22+
# HTTP setup
23+
otlphttp/hdx:
24+
endpoint: 'http://localhost:4318'
25+
headers:
26+
authorization: <YOUR_INGESTION_API_KEY>
27+
compression: gzip
28+
29+
# gRPC setup (alternative)
30+
otlp/hdx:
31+
endpoint: 'localhost:4317'
32+
headers:
33+
authorization: <YOUR_API_INGESTION_KEY>
34+
compression: gzip
35+
service:
36+
telemetry:
37+
metrics:
38+
address: 0.0.0.0:9888 # Modified as 2 collectors running on same host
39+
pipelines:
40+
logs:
41+
receivers: [filelog]
42+
processors: [batch]
43+
exporters: [otlphttp/hdx]

contribute/style-guide.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,73 @@ SELECT * FROM system.contributors;
112112
\```
113113
```
114114

115+
Note: in the snippet above `\` is used only for formatting purposes in this guide.
116+
You should not include it when you write markdown.
117+
115118
Code blocks:
116119
- Should always have a language defined immediately next to the opening 3
117120
backticks, without any space.
118121
- Have a title (optional) such as 'Query' or 'Response'
119122
- Use language `response` if it is for the result of a query.
120123

124+
#### Importing code from files or URLs
125+
126+
There are a few additional parameters you can include on a code block if you want
127+
to import code.
128+
129+
To import from a file use `file=`:
130+
131+
```text
132+
\```python file=code_snippets/integrations/example.py
133+
Code will be inserted here
134+
\```
135+
```
136+
137+
When `yarn build` is run, the code from the file will be inserted as text into
138+
the code block.
139+
140+
To import from a url use `url=`:
141+
142+
```text
143+
\```python url=https://raw.githubusercontent.com/ClickHouse/clickhouse-connect/refs/heads/main/examples/pandas_examples.py
144+
Code will be inserted here
145+
\```
146+
```
147+
148+
You should commit the code inserted to the snippet as we want people (or LLMs)
149+
reading the markdown to be able to see the code. The advantage of importing code
150+
to snippets this way is that you can test your snippets externally or store them
151+
wherever you want.
152+
153+
If you want to only import a section from a file, surround the section with `docs-start`
154+
and `docs-end` comments, for example:
155+
156+
```python
157+
a = 200
158+
b = 33
159+
#docs-start
160+
if b > a:
161+
print("b is greater than a")
162+
elif a == b:
163+
print("a and b are equal")
164+
else:
165+
print("a is greater than b")
166+
#docs-end
167+
```
168+
169+
Only the code between those comments will be pulled.
170+
171+
If you want to make multiple code snippets from one file then you can use the `snippet` parameter:
172+
173+
```markdown
174+
175+
\```python url=https://raw.githubusercontent.com/ClickHouse/clickhouse-connect/refs/heads/main/examples/pandas_examples.py snippet=1
176+
Code will be inserted here
177+
\```
178+
```
179+
180+
You will then use `docs-start-1`, `docs-end-1` comments for the first snippet, `docs-start-2`, `docs-end-2` for the second snippet and so on.
181+
121182
### Highlighting
122183

123184
You can highlight lines in a code block using the following keywords:

docs/use-cases/observability/clickstack/ingesting-data/collector.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,7 @@ The following configuration shows collection of this [unstructured log file](htt
161161

162162
Note the use of operators to extract structure from the log lines (`regex_parser`) and filter events, along with a processor to batch events and limit memory usage.
163163

164-
```yaml
165-
# config-unstructured-logs-with-processor.yaml
164+
```yaml file=code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml
166165
receivers:
167166
filelog:
168167
include:
@@ -190,7 +189,7 @@ exporters:
190189
headers:
191190
authorization: <YOUR_INGESTION_API_KEY>
192191
compression: gzip
193-
192+
194193
# gRPC setup (alternative)
195194
otlp/hdx:
196195
endpoint: 'localhost:4317'

docusaurus.config.en.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import chHeader from "./plugins/header.js";
55
import fixLinks from "./src/hooks/fixLinks.js";
66
const path = require('path');
77
const remarkCustomBlocks = require('./plugins/remark-custom-blocks');
8+
const codeImportPlugin = require('./plugins/code-import-plugin');
89

910
// Import custom plugins
1011
const { customParseFrontMatter } = require('./plugins/frontmatter-validation/customParseFrontMatter');
@@ -355,6 +356,10 @@ const config = {
355356
[
356357
'./plugins/tailwind-config.js',
357358
{}
359+
],
360+
[
361+
codeImportPlugin,
362+
{}
358363
]
359364
],
360365
customFields: {

plugins/code-import-plugin.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const glob = require('glob');
4+
const https = require('https');
5+
const http = require('http');
6+
7+
// Helper function to fetch content from URL
8+
function fetchUrl(url) {
9+
return new Promise((resolve, reject) => {
10+
const client = url.startsWith('https:') ? https : http;
11+
12+
client.get(url, (res) => {
13+
if (res.statusCode !== 200) {
14+
reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
15+
return;
16+
}
17+
18+
let data = '';
19+
res.on('data', chunk => data += chunk);
20+
res.on('end', () => resolve(data));
21+
}).on('error', reject);
22+
});
23+
}
24+
25+
// Helper function to extract snippet from content using comment markers
26+
function extractSnippet(content, snippetId = null) {
27+
const lines = content.split('\n');
28+
29+
// Define comment patterns for different languages
30+
const commentPatterns = [
31+
// Hash-style comments (Python, Ruby, Shell, YAML, etc.)
32+
{ start: `#docs-start${snippetId ? `-${snippetId}` : ''}`, end: `#docs-end${snippetId ? `-${snippetId}` : ''}` },
33+
// Double-slash comments (JavaScript, Java, C++, etc.)
34+
{ start: `//docs-start${snippetId ? `-${snippetId}` : ''}`, end: `//docs-end${snippetId ? `-${snippetId}` : ''}` },
35+
// Block comments (CSS, SQL, etc.)
36+
{ start: `/*docs-start${snippetId ? `-${snippetId}` : ''}*/`, end: `/*docs-end${snippetId ? `-${snippetId}` : ''}*/` },
37+
// XML/HTML comments
38+
{ start: `<!--docs-start${snippetId ? `-${snippetId}` : ''}-->`, end: `<!--docs-end${snippetId ? `-${snippetId}` : ''}-->` }
39+
];
40+
41+
for (const pattern of commentPatterns) {
42+
let startIndex = -1;
43+
let endIndex = -1;
44+
45+
for (let i = 0; i < lines.length; i++) {
46+
const line = lines[i].trim();
47+
if (line.includes(pattern.start)) {
48+
startIndex = i + 1; // Start from the line after the start marker
49+
} else if (line.includes(pattern.end) && startIndex !== -1) {
50+
endIndex = i; // End at the line before the end marker
51+
break;
52+
}
53+
}
54+
55+
if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex) {
56+
return lines.slice(startIndex, endIndex).join('\n');
57+
}
58+
}
59+
60+
// If no snippet markers found, return original content
61+
return content;
62+
}
63+
64+
function codeImportPlugin(context, options) {
65+
return {
66+
name: 'code-import-plugin',
67+
async loadContent() {
68+
// Find all markdown files in docs directory that might contain code imports
69+
const docsPath = path.join(context.siteDir, 'docs');
70+
71+
const markdownFiles = [
72+
...glob.sync('**/*.md', { cwd: docsPath, absolute: true }),
73+
...glob.sync('**/*.mdx', { cwd: docsPath, absolute: true }),
74+
];
75+
76+
// Process each markdown file for code imports
77+
const processedFiles = [];
78+
79+
for (const filePath of markdownFiles) {
80+
try {
81+
let content = fs.readFileSync(filePath, 'utf8');
82+
let modified = false;
83+
84+
// Process code blocks with file= or url= syntax
85+
const fileUrlRegex = /```(\w+)?\s*((?:file|url)=[^\s\n]+)([^\n]*)\n([^`]*?)```/g;
86+
const matches = [...content.matchAll(fileUrlRegex)];
87+
88+
for (const match of matches) {
89+
const [fullMatch, lang, param, additionalMeta, existingContent] = match;
90+
91+
// Parse snippet parameter from additional metadata
92+
const snippetMatch = additionalMeta.match(/snippet=(\w+)/);
93+
const snippetId = snippetMatch ? snippetMatch[1] : null;
94+
95+
try {
96+
let importedContent;
97+
98+
if (param.startsWith('file=')) {
99+
// Handle file import
100+
const importPath = param.replace('file=', '');
101+
const absoluteImportPath = path.resolve(context.siteDir, importPath);
102+
const rawContent = fs.readFileSync(absoluteImportPath, 'utf8');
103+
importedContent = extractSnippet(rawContent, snippetId);
104+
} else if (param.startsWith('url=')) {
105+
// Handle URL import
106+
const url = param.replace('url=', '');
107+
try {
108+
const rawContent = await fetchUrl(url);
109+
importedContent = extractSnippet(rawContent, snippetId);
110+
} catch (urlError) {
111+
console.warn(`Could not fetch URL ${url} in ${filePath}: ${urlError.message}`);
112+
continue; // Skip this replacement if URL fetch fails
113+
}
114+
}
115+
116+
// Preserve the complete metadata
117+
const fullMeta = `${param}${additionalMeta}`;
118+
const metaStr = fullMeta ? ` ${fullMeta}` : '';
119+
const replacement = `\`\`\`${lang || ''}${metaStr}\n${importedContent}\n\`\`\``;
120+
121+
content = content.replace(fullMatch, replacement);
122+
modified = true;
123+
124+
} catch (error) {
125+
console.warn(`Could not process ${param} in ${filePath}: ${error.message}`);
126+
}
127+
}
128+
129+
if (modified) {
130+
processedFiles.push({
131+
path: filePath,
132+
content: content,
133+
originalPath: filePath
134+
});
135+
}
136+
} catch (error) {
137+
console.warn(`Error processing file ${filePath}: ${error.message}`);
138+
}
139+
}
140+
141+
return { processedFiles };
142+
},
143+
144+
async contentLoaded({ content, actions }) {
145+
const { processedFiles } = content;
146+
147+
// Write processed files back to disk during build
148+
for (const file of processedFiles) {
149+
try {
150+
fs.writeFileSync(file.path, file.content, 'utf8');
151+
console.log(`Processed code imports in: ${path.relative(context.siteDir, file.path)}`);
152+
} catch (error) {
153+
console.error(`Error writing processed file ${file.path}: ${error.message}`);
154+
}
155+
}
156+
}
157+
};
158+
}
159+
160+
module.exports = codeImportPlugin;

0 commit comments

Comments
 (0)