From a17a8a5b1d6964e41fac4a9e44060cc898b60a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Bl=C3=A4ttermann?= Date: Sun, 27 Nov 2022 01:40:38 +0100 Subject: [PATCH] Add code block handling --- website/package-lock.json | 12 ++ website/package.json | 2 + website/plugins/getProps.mjs | 3 +- website/plugins/remarkCodeBlocks.mjs | 12 +- website/src/components/code.js | 250 ++++++++++++++++++--------- 5 files changed, 190 insertions(+), 89 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index af557da4f..3c89ec8a8 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -16,6 +16,7 @@ "@types/node": "18.11.9", "@types/react": "18.0.25", "@types/react-dom": "18.0.8", + "acorn": "^8.8.1", "browser-monads": "^1.0.0", "classnames": "^2.3.2", "eslint": "8.27.0", @@ -25,6 +26,7 @@ "md-attr-parser": "^1.3.0", "next": "13.0.2", "next-mdx-remote": "^4.2.0", + "parse-numeric-range": "^1.3.0", "prettier": "^2.7.1", "prop-types": "^15.8.1", "react": "18.2.0", @@ -4303,6 +4305,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8765,6 +8772,11 @@ "is-hexadecimal": "^2.0.0" } }, + "parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", diff --git a/website/package.json b/website/package.json index 92fac38c6..94678bda1 100644 --- a/website/package.json +++ b/website/package.json @@ -21,6 +21,7 @@ "@types/node": "18.11.9", "@types/react": "18.0.25", "@types/react-dom": "18.0.8", + "acorn": "^8.8.1", "browser-monads": "^1.0.0", "classnames": "^2.3.2", "eslint": "8.27.0", @@ -30,6 +31,7 @@ "md-attr-parser": "^1.3.0", "next": "13.0.2", "next-mdx-remote": "^4.2.0", + "parse-numeric-range": "^1.3.0", "prettier": "^2.7.1", "prop-types": "^15.8.1", "react": "18.2.0", diff --git a/website/plugins/getProps.mjs b/website/plugins/getProps.mjs index 1b9508ac0..6eb2fdc5c 100644 --- a/website/plugins/getProps.mjs +++ b/website/plugins/getProps.mjs @@ -17,7 +17,8 @@ const getProps = (estree) => { return {} } - const estreeBodyFirstNode = estree.body[0] + const estreeBodyFirstNode = + estree.body[0].type === 'BlockStatement' ? estree.body[0].body[0] : estree.body[0] if (estreeBodyFirstNode.type !== 'ExpressionStatement' || !estreeBodyFirstNode.expression) { return {} diff --git a/website/plugins/remarkCodeBlocks.mjs b/website/plugins/remarkCodeBlocks.mjs index bbf2fcd2c..9956f6767 100644 --- a/website/plugins/remarkCodeBlocks.mjs +++ b/website/plugins/remarkCodeBlocks.mjs @@ -1,10 +1,12 @@ /** * Support titles, line highlights and more for code blocks */ - +import { Parser } from 'acorn' import { visit } from 'unist-util-visit' import parseAttr from 'md-attr-parser' +import getProps from './getProps.mjs' + const defaultOptions = { defaultPrefix: '###', prefixMap: { @@ -48,7 +50,13 @@ function remarkCodeBlocks(userOptions = {}) { const data = node.data || (node.data = {}) const hProps = data.hProperties || (data.hProperties = {}) - node.data.hProperties = Object.assign({}, hProps, attrs) + + node.data.hProperties = Object.assign( + {}, + hProps, + attrs, + getProps(Parser.parse(node.meta, { ecmaVersion: 'latest' })) + ) } }) diff --git a/website/src/components/code.js b/website/src/components/code.js index b1f134501..6f5e437c6 100644 --- a/website/src/components/code.js +++ b/website/src/components/code.js @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react' +import React, { Fragment, useEffect, useState } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import highlightCode from 'gatsby-remark-prismjs/highlight-code.js' @@ -94,12 +94,48 @@ export const TypeAnnotation = ({ lang = 'python', link = true, children }) => { ) } -function replacePrompt(line, prompt, isFirst = false) { - let result = line - const hasPrompt = result.startsWith(`${prompt} `) - const showPrompt = hasPrompt || isFirst - if (hasPrompt) result = result.slice(2) - return result && showPrompt ? `${result}` : result +const splitLines = (children) => { + const listChildrenPerLine = [] + + if (typeof children === 'string') { + listChildrenPerLine.push(...children.split('\n')) + } else { + listChildrenPerLine.push([]) + let indexLine = 0 + if (Array.isArray(children)) { + children.forEach((child) => { + if (typeof child === 'string' && child.includes('\n')) { + const listString = child.split('\n') + listString.forEach((string, index) => { + listChildrenPerLine[indexLine].push(string) + + if (index !== listString.length - 1) { + indexLine += 1 + listChildrenPerLine[indexLine] = [] + } + }) + } else { + listChildrenPerLine[indexLine].push(child) + } + }) + } else { + listChildrenPerLine[indexLine].push(children) + indexLine += 1 + listChildrenPerLine[indexLine] = [] + } + } + + const listLine = listChildrenPerLine[listChildrenPerLine.length - 1] + if (listLine === '' || (listLine.length === 1 && listLine[0] === '')) { + listChildrenPerLine.pop() + } + + return listChildrenPerLine.map((childrenPerLine, index) => ( + <> + {childrenPerLine} + {index !== listChildrenPerLine.length - 1 && '\n'} + + )) } function parseArgs(raw) { @@ -123,82 +159,128 @@ function parseArgs(raw) { return result } -function convertLine(line, i) { - const cliRegex = /^(\$ )?python -m spacy/ - if (cliRegex.test(line)) { - const text = line.replace(cliRegex, '') - const args = parseArgs(text) - const cmd = Object.keys(args).map((key, i) => { - const value = args[key] - return value === null || value === true || i === 0 ? key : `${key} ${value}` - }) - return ( - - - python -m - {' '} - spacy{' '} - {cmd.map((item, j) => { - const isCmd = j === 0 - const url = isCmd ? `/api/cli#${item.replace(' ', '-')}` : null - const isAbstract = isString(item) && /^\[(.+)\]$/.test(item) - const itemClassNames = classNames(classes['cli-arg'], { - [classes['cli-arg-highlight']]: isCmd, - [classes['cli-arg-emphasis']]: isAbstract, - }) - const text = isAbstract ? item.slice(1, -1) : item - return ( - - {j !== 0 && ' '} - - - - - ) - })} - - ) +const flattenReact = (children) => { + if (children === null || children === undefined || children === false) { + return [] } - const htmlLine = replacePrompt(highlightCode('bash', line), '$') - return htmlToReact(htmlLine) + + if (typeof children === 'string') { + return [children] + } + + if (children.props) { + return flattenReact(children.props.children) + } + + return children.flatMap(flattenReact) } -function formatCode(html, lang, prompt) { - if (lang === 'cli') { - const lines = html - .trim() - .split('\n') - .map((line) => - line - .split(' | ') - .map((l, i) => convertLine(l, i)) - .map((l, j) => ( - - {j !== 0 && | } - {l} - - )) - ) - return lines.map((line, i) => ( - - {i !== 0 &&
} - {line} -
- )) +const checkoutForComment = (line) => { + const lineParts = line.split(' # ') + + if (lineParts.length !== 2) { + return line } - const result = html - .split('\n') - .map((line, i) => { - let newLine = prompt ? replacePrompt(line, prompt, i === 0) : line - if (lang === 'diff' && !line.startsWith('<')) { - newLine = highlightCode('python', line) - } - return newLine - }) - .join('\n') - return htmlToReact(result) + + return ( + <> + {lineParts[0]} + {` `} + + {`# `} + {lineParts[1]} + + + ) +} + +const convertLine = (line, prompt) => { + const lineFlat = flattenReact(line).join('') + if (!lineFlat.startsWith(`${prompt} `)) { + return line + } + + const lineWithoutPrompt = lineFlat.slice(prompt.length + 1) + + const cliRegex = /^python -m spacy/ + + if (!cliRegex.test(lineWithoutPrompt)) { + return {checkoutForComment(lineWithoutPrompt)} + } + + const text = lineWithoutPrompt.replace(cliRegex, '') + const args = parseArgs(text) + const cmd = Object.keys(args).map((key, i) => { + const value = args[key] + return value === null || value === true || i === 0 ? key : `${key} ${value}` + }) + return ( + + python -m spacy{' '} + {cmd.map((item, j) => { + const isCmd = j === 0 + const url = isCmd ? `/api/cli#${item.replace(' ', '-')}` : null + const isAbstract = isString(item) && /^\[(.+)\]$/.test(item) + const itemClassNames = classNames(classes['cli-arg'], { + [classes['cli-arg-highlight']]: isCmd, + [classes['cli-arg-emphasis']]: isAbstract, + }) + const text = isAbstract ? item.slice(1, -1) : item + return ( + + {j !== 0 && ' '} + + + + + ) + })} + + ) +} + +const addLineHighlight = (children, highlight) => { + if (!highlight) { + return children + } + const listHighlight = rangeParser(highlight) + + if (listHighlight.length === 0) { + return children + } + + return children.map((child, index) => { + const isHighlight = listHighlight.includes(index + 1) + return ( + + {child} + + ) + }) +} + +const CodeHighlighted = ({ children, highlight }) => { + const [html, setHtml] = useState() + + useEffect( + () => + setHtml( + addLineHighlight( + splitLines(children).map((line) => convertLine(line, '$')), + highlight + ) + ), + [children, highlight] + ) + + return <>{html} } export class Code extends React.Component { @@ -255,16 +337,12 @@ export class Code extends React.Component { ) } - const codeText = Array.isArray(children) ? children.join('') : children || '' - const highlightRange = highlight ? rangeParser.parse(highlight).filter((n) => n > 0) : [] - const rawHtml = ['none', 'cli'].includes(lang) - ? codeText - : highlightCode(lang, codeText, highlightRange) - const html = formatCode(rawHtml, lang, prompt) return ( <> {title &&

{title}

} - {html} + + {children} + ) }