From cdd0176850f14894504bd513bae202cbadd1e016 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:56:07 +0200 Subject: [PATCH 1/9] add ability to import code block from a file --- ...nfig-unstructured-logs-with-processor.yaml | 43 +++++++++++++++++ .../clickstack/ingesting-data/collector.md | 47 +------------------ docusaurus.config.en.js | 3 +- package.json | 1 + plugins/remark-code-import.js | 47 +++++++++++++++++++ yarn.lock | 8 ++++ 6 files changed, 103 insertions(+), 46 deletions(-) create mode 100644 code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml create mode 100644 plugins/remark-code-import.js diff --git a/code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml b/code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml new file mode 100644 index 00000000000..2eae63144a8 --- /dev/null +++ b/code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml @@ -0,0 +1,43 @@ +receivers: + filelog: + include: + - /opt/data/logs/access-unstructured.log + start_at: beginning + operators: + - type: regex_parser + regex: '^(?P[\d.]+)\s+-\s+-\s+\[(?P[^\]]+)\]\s+"(?P[A-Z]+)\s+(?P[^\s]+)\s+HTTP/[^\s]+"\s+(?P\d+)\s+(?P\d+)\s+"(?P[^"]*)"\s+"(?P[^"]*)"' + timestamp: + parse_from: attributes.timestamp + layout: '%d/%b/%Y:%H:%M:%S %z' + #22/Jan/2019:03:56:14 +0330 +processors: + batch: + timeout: 1s + send_batch_size: 100 + memory_limiter: + check_interval: 1s + limit_mib: 2048 + spike_limit_mib: 256 +exporters: + # HTTP setup + otlphttp/hdx: + endpoint: 'http://localhost:4318' + headers: + authorization: + compression: gzip + + # gRPC setup (alternative) + otlp/hdx: + endpoint: 'localhost:4317' + headers: + authorization: + compression: gzip +service: + telemetry: + metrics: + address: 0.0.0.0:9888 # Modified as 2 collectors running on same host + pipelines: + logs: + receivers: [filelog] + processors: [batch] + exporters: [otlphttp/hdx] \ No newline at end of file diff --git a/docs/use-cases/observability/clickstack/ingesting-data/collector.md b/docs/use-cases/observability/clickstack/ingesting-data/collector.md index 8e61466701c..cb3f8c93da6 100644 --- a/docs/use-cases/observability/clickstack/ingesting-data/collector.md +++ b/docs/use-cases/observability/clickstack/ingesting-data/collector.md @@ -161,51 +161,8 @@ The following configuration shows collection of this [unstructured log file](htt 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. -```yaml -# config-unstructured-logs-with-processor.yaml -receivers: - filelog: - include: - - /opt/data/logs/access-unstructured.log - start_at: beginning - operators: - - type: regex_parser - regex: '^(?P[\d.]+)\s+-\s+-\s+\[(?P[^\]]+)\]\s+"(?P[A-Z]+)\s+(?P[^\s]+)\s+HTTP/[^\s]+"\s+(?P\d+)\s+(?P\d+)\s+"(?P[^"]*)"\s+"(?P[^"]*)"' - timestamp: - parse_from: attributes.timestamp - layout: '%d/%b/%Y:%H:%M:%S %z' - #22/Jan/2019:03:56:14 +0330 -processors: - batch: - timeout: 1s - send_batch_size: 100 - memory_limiter: - check_interval: 1s - limit_mib: 2048 - spike_limit_mib: 256 -exporters: - # HTTP setup - otlphttp/hdx: - endpoint: 'http://localhost:4318' - headers: - authorization: - compression: gzip - - # gRPC setup (alternative) - otlp/hdx: - endpoint: 'localhost:4317' - headers: - authorization: - compression: gzip -service: - telemetry: - metrics: - address: 0.0.0.0:9888 # Modified as 2 collectors running on same host - pipelines: - logs: - receivers: [filelog] - processors: [batch] - exporters: [otlphttp/hdx] +```yaml file=code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml +place holder text ``` Note the need to include an [authorization header containing your ingestion API key](#securing-the-collector) in any OTLP communication. diff --git a/docusaurus.config.en.js b/docusaurus.config.en.js index b930c2a57a2..6914b2c0506 100644 --- a/docusaurus.config.en.js +++ b/docusaurus.config.en.js @@ -5,6 +5,7 @@ import chHeader from "./plugins/header.js"; import fixLinks from "./src/hooks/fixLinks.js"; const path = require('path'); const remarkCustomBlocks = require('./plugins/remark-custom-blocks'); +const remarkCodeImport = require('./plugins/remark-code-import'); // Import custom plugins const { customParseFrontMatter } = require('./plugins/frontmatter-validation/customParseFrontMatter'); @@ -152,7 +153,7 @@ const config = { showLastUpdateTime: false, sidebarCollapsed: true, routeBasePath: "/", - remarkPlugins: [math, remarkCustomBlocks, glossaryTransformer], + remarkPlugins: [math, remarkCustomBlocks, glossaryTransformer, [remarkCodeImport, { baseDir: __dirname }]], beforeDefaultRemarkPlugins: [fixLinks], rehypePlugins: [katex], }, diff --git a/package.json b/package.json index 27de1e81216..bfe528bca18 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "node-fetch": "^3.3.2", "numeral": "^2.0.6", "prism-react-renderer": "^2.4.1", + "raw-loader": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-medium-image-zoom": "^5.2.14", diff --git a/plugins/remark-code-import.js b/plugins/remark-code-import.js new file mode 100644 index 00000000000..700cb0410d8 --- /dev/null +++ b/plugins/remark-code-import.js @@ -0,0 +1,47 @@ +const fs = require('fs'); +const path = require('path'); +const { visit } = require('unist-util-visit'); + +function createCodeImportPlugin(options = {}) { + const { baseDir = process.cwd() } = options; + return function transformer(tree, file) { + visit(tree, 'code', (node) => { + const { lang, meta } = node; + + // Check if file= is in meta or lang + let fileMatch = null; + let filePath = null; + + if (meta) { + fileMatch = meta.match(/file=([^\s]+)/); + } + if (!fileMatch && lang) { + fileMatch = lang.match(/file=([^\s]+)/); + if (fileMatch) { + // Extract real language (everything before file=) + const realLang = lang.split(/\s+file=/)[0].trim(); + node.lang = realLang; + } + } + + if (!fileMatch) return; + + filePath = fileMatch[1]; + const absolutePath = path.resolve(baseDir, filePath); + + try { + const content = fs.readFileSync(absolutePath, 'utf8'); + node.value = content; + + // Clean up meta if it had file= + if (meta && meta.includes('file=')) { + node.meta = meta.replace(/file=[^\s]+\s*/, '').trim() || null; + } + } catch (error) { + console.warn(`Could not read file ${filePath}: ${error.message}`); + } + }); + }; +}; + +module.exports = createCodeImportPlugin; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 52a748f0b58..fdd47053647 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11754,6 +11754,14 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +raw-loader@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" + integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + rc@1.2.8, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" From cac32300e8270ee0f624a0f6b06feb65bac75b1d Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Wed, 6 Aug 2025 21:03:13 +0200 Subject: [PATCH 2/9] Update config-unstructured-logs-with-processor.yaml --- .../ClickStack/config-unstructured-logs-with-processor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml b/code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml index 2eae63144a8..25059c60598 100644 --- a/code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml +++ b/code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml @@ -40,4 +40,4 @@ service: logs: receivers: [filelog] processors: [batch] - exporters: [otlphttp/hdx] \ No newline at end of file + exporters: [otlphttp/hdx] From 4388250e7d891131fe987af8b5a4f1a8ab2cca3f Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Wed, 6 Aug 2025 21:03:35 +0200 Subject: [PATCH 3/9] Update remark-code-import.js --- plugins/remark-code-import.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/remark-code-import.js b/plugins/remark-code-import.js index 700cb0410d8..f050bceb100 100644 --- a/plugins/remark-code-import.js +++ b/plugins/remark-code-import.js @@ -44,4 +44,4 @@ function createCodeImportPlugin(options = {}) { }; }; -module.exports = createCodeImportPlugin; \ No newline at end of file +module.exports = createCodeImportPlugin; From 620ae8045f8ee321636f30c7adcaa00a0d81f3f3 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:16:00 +0200 Subject: [PATCH 4/9] modify script plugin so that snippets update on build permanently --- docusaurus.config.en.js | 8 +++- package.json | 1 - plugins/code-import-plugin.js | 75 +++++++++++++++++++++++++++++++++++ plugins/remark-code-import.js | 47 ---------------------- yarn.lock | 8 ---- 5 files changed, 81 insertions(+), 58 deletions(-) create mode 100644 plugins/code-import-plugin.js delete mode 100644 plugins/remark-code-import.js diff --git a/docusaurus.config.en.js b/docusaurus.config.en.js index 6914b2c0506..ea2ff37236c 100644 --- a/docusaurus.config.en.js +++ b/docusaurus.config.en.js @@ -5,7 +5,7 @@ import chHeader from "./plugins/header.js"; import fixLinks from "./src/hooks/fixLinks.js"; const path = require('path'); const remarkCustomBlocks = require('./plugins/remark-custom-blocks'); -const remarkCodeImport = require('./plugins/remark-code-import'); +const codeImportPlugin = require('./plugins/code-import-plugin'); // Import custom plugins const { customParseFrontMatter } = require('./plugins/frontmatter-validation/customParseFrontMatter'); @@ -153,7 +153,7 @@ const config = { showLastUpdateTime: false, sidebarCollapsed: true, routeBasePath: "/", - remarkPlugins: [math, remarkCustomBlocks, glossaryTransformer, [remarkCodeImport, { baseDir: __dirname }]], + remarkPlugins: [math, remarkCustomBlocks, glossaryTransformer], beforeDefaultRemarkPlugins: [fixLinks], rehypePlugins: [katex], }, @@ -356,6 +356,10 @@ const config = { [ './plugins/tailwind-config.js', {} + ], + [ + codeImportPlugin, + {} ] ], customFields: { diff --git a/package.json b/package.json index bfe528bca18..27de1e81216 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "node-fetch": "^3.3.2", "numeral": "^2.0.6", "prism-react-renderer": "^2.4.1", - "raw-loader": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-medium-image-zoom": "^5.2.14", diff --git a/plugins/code-import-plugin.js b/plugins/code-import-plugin.js new file mode 100644 index 00000000000..0ea5d07ff3f --- /dev/null +++ b/plugins/code-import-plugin.js @@ -0,0 +1,75 @@ +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); + +function codeImportPlugin(context, options) { + return { + name: 'code-import-plugin', + async loadContent() { + // Find all markdown files in docs directory that might contain code imports + const docsPath = path.join(context.siteDir, 'docs'); + + const markdownFiles = [ + ...glob.sync('**/*.md', { cwd: docsPath, absolute: true }), + ...glob.sync('**/*.mdx', { cwd: docsPath, absolute: true }), + ]; + + // Process each markdown file for code imports + const processedFiles = []; + + for (const filePath of markdownFiles) { + try { + let content = fs.readFileSync(filePath, 'utf8'); + let modified = false; + + // Process code blocks with file= syntax + content = content.replace(/```(\w+)?\s*(file=[^\s\n]+)([^\n]*)\n([^`]*?)```/g, (match, lang, fileParam, additionalMeta, existingContent) => { + try { + const importPath = fileParam.replace('file=', ''); + const absoluteImportPath = path.resolve(context.siteDir, importPath); + const importedContent = fs.readFileSync(absoluteImportPath, 'utf8'); + modified = true; + + // Preserve the complete metadata including file= and any additional parameters + const fullMeta = `${fileParam}${additionalMeta}`; + const metaStr = fullMeta ? ` ${fullMeta}` : ''; + + return `\`\`\`${lang || ''}${metaStr}\n${importedContent}\`\`\``; + } catch (error) { + console.warn(`Could not import file ${importPath} in ${filePath}: ${error.message}`); + return match; // Return original if import fails + } + }); + + if (modified) { + processedFiles.push({ + path: filePath, + content: content, + originalPath: filePath + }); + } + } catch (error) { + console.warn(`Error processing file ${filePath}: ${error.message}`); + } + } + + return { processedFiles }; + }, + + async contentLoaded({ content, actions }) { + const { processedFiles } = content; + + // Write processed files back to disk during build + for (const file of processedFiles) { + try { + fs.writeFileSync(file.path, file.content, 'utf8'); + console.log(`Processed code imports in: ${path.relative(context.siteDir, file.path)}`); + } catch (error) { + console.error(`Error writing processed file ${file.path}: ${error.message}`); + } + } + } + }; +} + +module.exports = codeImportPlugin; \ No newline at end of file diff --git a/plugins/remark-code-import.js b/plugins/remark-code-import.js deleted file mode 100644 index f050bceb100..00000000000 --- a/plugins/remark-code-import.js +++ /dev/null @@ -1,47 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const { visit } = require('unist-util-visit'); - -function createCodeImportPlugin(options = {}) { - const { baseDir = process.cwd() } = options; - return function transformer(tree, file) { - visit(tree, 'code', (node) => { - const { lang, meta } = node; - - // Check if file= is in meta or lang - let fileMatch = null; - let filePath = null; - - if (meta) { - fileMatch = meta.match(/file=([^\s]+)/); - } - if (!fileMatch && lang) { - fileMatch = lang.match(/file=([^\s]+)/); - if (fileMatch) { - // Extract real language (everything before file=) - const realLang = lang.split(/\s+file=/)[0].trim(); - node.lang = realLang; - } - } - - if (!fileMatch) return; - - filePath = fileMatch[1]; - const absolutePath = path.resolve(baseDir, filePath); - - try { - const content = fs.readFileSync(absolutePath, 'utf8'); - node.value = content; - - // Clean up meta if it had file= - if (meta && meta.includes('file=')) { - node.meta = meta.replace(/file=[^\s]+\s*/, '').trim() || null; - } - } catch (error) { - console.warn(`Could not read file ${filePath}: ${error.message}`); - } - }); - }; -}; - -module.exports = createCodeImportPlugin; diff --git a/yarn.lock b/yarn.lock index fdd47053647..52a748f0b58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11754,14 +11754,6 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -raw-loader@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" - integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - rc@1.2.8, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" From 1c48f18212476cef96143ec64e5fbc643a790a7b Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:16:20 +0200 Subject: [PATCH 5/9] use external file for code snippets --- .../clickstack/ingesting-data/collector.md | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/docs/use-cases/observability/clickstack/ingesting-data/collector.md b/docs/use-cases/observability/clickstack/ingesting-data/collector.md index cb3f8c93da6..e55460191a0 100644 --- a/docs/use-cases/observability/clickstack/ingesting-data/collector.md +++ b/docs/use-cases/observability/clickstack/ingesting-data/collector.md @@ -162,7 +162,49 @@ The following configuration shows collection of this [unstructured log file](htt 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. ```yaml file=code_snippets/ClickStack/config-unstructured-logs-with-processor.yaml -place holder text +receivers: + filelog: + include: + - /opt/data/logs/access-unstructured.log + start_at: beginning + operators: + - type: regex_parser + regex: '^(?P[\d.]+)\s+-\s+-\s+\[(?P[^\]]+)\]\s+"(?P[A-Z]+)\s+(?P[^\s]+)\s+HTTP/[^\s]+"\s+(?P\d+)\s+(?P\d+)\s+"(?P[^"]*)"\s+"(?P[^"]*)"' + timestamp: + parse_from: attributes.timestamp + layout: '%d/%b/%Y:%H:%M:%S %z' + #22/Jan/2019:03:56:14 +0330 +processors: + batch: + timeout: 1s + send_batch_size: 100 + memory_limiter: + check_interval: 1s + limit_mib: 2048 + spike_limit_mib: 256 +exporters: + # HTTP setup + otlphttp/hdx: + endpoint: 'http://localhost:4318' + headers: + authorization: + compression: gzip + + # gRPC setup (alternative) + otlp/hdx: + endpoint: 'localhost:4317' + headers: + authorization: + compression: gzip +service: + telemetry: + metrics: + address: 0.0.0.0:9888 # Modified as 2 collectors running on same host + pipelines: + logs: + receivers: [filelog] + processors: [batch] + exporters: [otlphttp/hdx] ``` Note the need to include an [authorization header containing your ingestion API key](#securing-the-collector) in any OTLP communication. From c0644d2ae9f903865c2adf219c925a8d89fd7cbe Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:32:29 +0200 Subject: [PATCH 6/9] Update style guide to include new import methods and add ability to import from a URL --- contribute/style-guide.md | 32 ++++++++++++++++++ plugins/code-import-plugin.js | 64 ++++++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/contribute/style-guide.md b/contribute/style-guide.md index 02519179d42..69bec9e15fe 100644 --- a/contribute/style-guide.md +++ b/contribute/style-guide.md @@ -112,12 +112,44 @@ SELECT * FROM system.contributors; \``` ``` +Note: in the snippet above `\` is used only for formatting purposes in this guide. +You should not include it when you write markdown. + Code blocks: - Should always have a language defined immediately next to the opening 3 backticks, without any space. - Have a title (optional) such as 'Query' or 'Response' - Use language `response` if it is for the result of a query. +#### Importing code from files or URLs + +There are a few additional parameters you can include on a code block if you want +to import code. + +To import from a file use `file=`: + +```text +\```python file=code_snippets/integrations/example.py +Code will be inserted here +\``` +``` + +When `yarn build` is run, the code from the file will be inserted as text into +the code block. + +To import from a url use `url=`: + +```text +\```python url=https://raw.githubusercontent.com/ClickHouse/clickhouse-connect/refs/heads/main/examples/pandas_examples.py +Code will be inserted here +\``` +``` + +You should commit the code inserted to the snippet as we want people (or LLMs) +reading the markdown to be able to see the code. The advantage of importing code +to snippets this way is that you can test your snippets externally or store them +wherever you want. + ### Highlighting You can highlight lines in a code block using the following keywords: diff --git a/plugins/code-import-plugin.js b/plugins/code-import-plugin.js index 0ea5d07ff3f..48920eeebd8 100644 --- a/plugins/code-import-plugin.js +++ b/plugins/code-import-plugin.js @@ -1,6 +1,26 @@ const fs = require('fs'); const path = require('path'); const glob = require('glob'); +const https = require('https'); +const http = require('http'); + +// Helper function to fetch content from URL +function fetchUrl(url) { + return new Promise((resolve, reject) => { + const client = url.startsWith('https:') ? https : http; + + client.get(url, (res) => { + if (res.statusCode !== 200) { + reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + return; + } + + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => resolve(data)); + }).on('error', reject); + }); +} function codeImportPlugin(context, options) { return { @@ -22,24 +42,44 @@ function codeImportPlugin(context, options) { let content = fs.readFileSync(filePath, 'utf8'); let modified = false; - // Process code blocks with file= syntax - content = content.replace(/```(\w+)?\s*(file=[^\s\n]+)([^\n]*)\n([^`]*?)```/g, (match, lang, fileParam, additionalMeta, existingContent) => { + // Process code blocks with file= or url= syntax + const fileUrlRegex = /```(\w+)?\s*((?:file|url)=[^\s\n]+)([^\n]*)\n([^`]*?)```/g; + const matches = [...content.matchAll(fileUrlRegex)]; + + for (const match of matches) { + const [fullMatch, lang, param, additionalMeta, existingContent] = match; + try { - const importPath = fileParam.replace('file=', ''); - const absoluteImportPath = path.resolve(context.siteDir, importPath); - const importedContent = fs.readFileSync(absoluteImportPath, 'utf8'); - modified = true; + let importedContent; + + if (param.startsWith('file=')) { + // Handle file import + const importPath = param.replace('file=', ''); + const absoluteImportPath = path.resolve(context.siteDir, importPath); + importedContent = fs.readFileSync(absoluteImportPath, 'utf8'); + } else if (param.startsWith('url=')) { + // Handle URL import + const url = param.replace('url=', ''); + try { + importedContent = await fetchUrl(url); + } catch (urlError) { + console.warn(`Could not fetch URL ${url} in ${filePath}: ${urlError.message}`); + continue; // Skip this replacement if URL fetch fails + } + } - // Preserve the complete metadata including file= and any additional parameters - const fullMeta = `${fileParam}${additionalMeta}`; + // Preserve the complete metadata + const fullMeta = `${param}${additionalMeta}`; const metaStr = fullMeta ? ` ${fullMeta}` : ''; + const replacement = `\`\`\`${lang || ''}${metaStr}\n${importedContent}\`\`\``; + + content = content.replace(fullMatch, replacement); + modified = true; - return `\`\`\`${lang || ''}${metaStr}\n${importedContent}\`\`\``; } catch (error) { - console.warn(`Could not import file ${importPath} in ${filePath}: ${error.message}`); - return match; // Return original if import fails + console.warn(`Could not process ${param} in ${filePath}: ${error.message}`); } - }); + } if (modified) { processedFiles.push({ From 9924be0373eb51ad3408000776a7b18e15635279 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:35:42 +0200 Subject: [PATCH 7/9] Update code-import-plugin.js --- plugins/code-import-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/code-import-plugin.js b/plugins/code-import-plugin.js index 48920eeebd8..aea0253eb59 100644 --- a/plugins/code-import-plugin.js +++ b/plugins/code-import-plugin.js @@ -112,4 +112,4 @@ function codeImportPlugin(context, options) { }; } -module.exports = codeImportPlugin; \ No newline at end of file +module.exports = codeImportPlugin; From af62999966d8b1f6119972507e461ffdd54a1783 Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:08:13 +0200 Subject: [PATCH 8/9] add ability to import only a specified section --- plugins/code-import-plugin.js | 51 ++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/plugins/code-import-plugin.js b/plugins/code-import-plugin.js index aea0253eb59..e9ae6d6ad72 100644 --- a/plugins/code-import-plugin.js +++ b/plugins/code-import-plugin.js @@ -22,6 +22,45 @@ function fetchUrl(url) { }); } +// Helper function to extract snippet from content using comment markers +function extractSnippet(content, snippetId = null) { + const lines = content.split('\n'); + + // Define comment patterns for different languages + const commentPatterns = [ + // Hash-style comments (Python, Ruby, Shell, YAML, etc.) + { start: `#docs-start${snippetId ? `-${snippetId}` : ''}`, end: `#docs-end${snippetId ? `-${snippetId}` : ''}` }, + // Double-slash comments (JavaScript, Java, C++, etc.) + { start: `//docs-start${snippetId ? `-${snippetId}` : ''}`, end: `//docs-end${snippetId ? `-${snippetId}` : ''}` }, + // Block comments (CSS, SQL, etc.) + { start: `/*docs-start${snippetId ? `-${snippetId}` : ''}*/`, end: `/*docs-end${snippetId ? `-${snippetId}` : ''}*/` }, + // XML/HTML comments + { start: ``, end: `` } + ]; + + for (const pattern of commentPatterns) { + let startIndex = -1; + let endIndex = -1; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (line.includes(pattern.start)) { + startIndex = i + 1; // Start from the line after the start marker + } else if (line.includes(pattern.end) && startIndex !== -1) { + endIndex = i; // End at the line before the end marker + break; + } + } + + if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex) { + return lines.slice(startIndex, endIndex).join('\n'); + } + } + + // If no snippet markers found, return original content + return content; +} + function codeImportPlugin(context, options) { return { name: 'code-import-plugin', @@ -49,6 +88,10 @@ function codeImportPlugin(context, options) { for (const match of matches) { const [fullMatch, lang, param, additionalMeta, existingContent] = match; + // Parse snippet parameter from additional metadata + const snippetMatch = additionalMeta.match(/snippet=(\w+)/); + const snippetId = snippetMatch ? snippetMatch[1] : null; + try { let importedContent; @@ -56,12 +99,14 @@ function codeImportPlugin(context, options) { // Handle file import const importPath = param.replace('file=', ''); const absoluteImportPath = path.resolve(context.siteDir, importPath); - importedContent = fs.readFileSync(absoluteImportPath, 'utf8'); + const rawContent = fs.readFileSync(absoluteImportPath, 'utf8'); + importedContent = extractSnippet(rawContent, snippetId); } else if (param.startsWith('url=')) { // Handle URL import const url = param.replace('url=', ''); try { - importedContent = await fetchUrl(url); + const rawContent = await fetchUrl(url); + importedContent = extractSnippet(rawContent, snippetId); } catch (urlError) { console.warn(`Could not fetch URL ${url} in ${filePath}: ${urlError.message}`); continue; // Skip this replacement if URL fetch fails @@ -71,7 +116,7 @@ function codeImportPlugin(context, options) { // Preserve the complete metadata const fullMeta = `${param}${additionalMeta}`; const metaStr = fullMeta ? ` ${fullMeta}` : ''; - const replacement = `\`\`\`${lang || ''}${metaStr}\n${importedContent}\`\`\``; + const replacement = `\`\`\`${lang || ''}${metaStr}\n${importedContent}\n\`\`\``; content = content.replace(fullMatch, replacement); modified = true; From b0b952801c75620330d48ea24b30af135f5fb15b Mon Sep 17 00:00:00 2001 From: Shaun Struwig <41984034+Blargian@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:14:05 +0200 Subject: [PATCH 9/9] update docs --- README.md | 4 ++-- contribute/style-guide.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e4957497a3d..214fb7c4c47 100644 --- a/README.md +++ b/README.md @@ -134,9 +134,9 @@ Please assign any pull request (PR) against an issue; this helps the docs team t 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). -### Style guidelines +### Style and contribution guidelines -For documentation style guidelines, see ["Style guide"](/contribute/style-guide.md). +For documentation style guidelines, see ["Style guide"](/contribute/style-guide.md). To check spelling and markdown is correct locally run: diff --git a/contribute/style-guide.md b/contribute/style-guide.md index 69bec9e15fe..60162654227 100644 --- a/contribute/style-guide.md +++ b/contribute/style-guide.md @@ -150,6 +150,35 @@ reading the markdown to be able to see the code. The advantage of importing code to snippets this way is that you can test your snippets externally or store them wherever you want. +If you want to only import a section from a file, surround the section with `docs-start` +and `docs-end` comments, for example: + +```python +a = 200 +b = 33 +#docs-start +if b > a: + print("b is greater than a") +elif a == b: + print("a and b are equal") +else: + print("a is greater than b") +#docs-end +``` + +Only the code between those comments will be pulled. + +If you want to make multiple code snippets from one file then you can use the `snippet` parameter: + +```markdown + +\```python url=https://raw.githubusercontent.com/ClickHouse/clickhouse-connect/refs/heads/main/examples/pandas_examples.py snippet=1 +Code will be inserted here +\``` +``` + +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. + ### Highlighting You can highlight lines in a code block using the following keywords: