import React, { Fragment } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import highlightCode from 'gatsby-remark-prismjs/highlight-code.js' import rangeParser from 'parse-numeric-range' import { StaticQuery, graphql } from 'gatsby' import { window } from 'browser-monads' import CUSTOM_TYPES from '../../meta/type-annotations.json' import { isString, htmlToReact } from './util' import Link, { OptionalLink } from './link' import GitHubCode from './github' import classes from '../styles/code.module.sass' const WRAP_THRESHOLD = 30 export default props => (
        
    
) export const Pre = props => { return
{props.children}
} export const InlineCode = ({ wrap = false, className, children, ...props }) => { const codeClassNames = classNames(classes.inlineCode, className, { [classes.wrap]: wrap || (isString(children) && children.length >= WRAP_THRESHOLD), }) return ( {children} ) } InlineCode.propTypes = { wrap: PropTypes.bool, className: PropTypes.string, children: PropTypes.node, } function linkType(el, showLink = true) { if (!isString(el) || !el.length) return el const elStr = el.trim() if (!elStr) return el const typeUrl = CUSTOM_TYPES[elStr] const url = typeUrl == true ? DEFAULT_TYPE_URL : typeUrl const ws = el[0] == ' ' return url && showLink ? ( {ws && ' '} {elStr} ) : ( el ) } export const TypeAnnotation = ({ lang = 'python', link = true, children }) => { // Hacky, but we're temporarily replacing a dot to prevent it from being split during highlighting const TMP_DOT = '۔' const code = Array.isArray(children) ? children.join('') : children || '' const [rawText, meta] = code.split(/(?= \(.+\)$)/) const rawStr = rawText.replace(/\./g, TMP_DOT) const rawHtml = lang === 'none' || !code ? code : highlightCode(lang, rawStr) const html = rawHtml.replace(new RegExp(TMP_DOT, 'g'), '.').replace(/\n/g, ' ') const result = htmlToReact(html) const elements = Array.isArray(result) ? result : [result] const annotClassNames = classNames( 'type-annotation', `language-${lang}`, classes.inlineCode, classes.typeAnnotation, { [classes.wrap]: code.length >= WRAP_THRESHOLD, } ) return ( {elements.map((el, i) => ( {linkType(el, !!link)} ))} {meta && {meta}} ) } 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 } function parseArgs(raw) { const commandGroups = ['init', 'debug', 'project'] let args = raw.split(' ').filter(arg => arg) const result = {} while (args.length) { let opt = args.shift() if (opt.length > 1 && opt.startsWith('-')) { const isFlag = !args.length || (args[0].length > 1 && args[0].startsWith('-')) result[opt] = isFlag ? true : args.shift() } else { const key = commandGroups.includes(opt) ? `${opt} ${args.shift()}` : opt result[key] = null } } return result } function formatCode(html, lang, prompt) { if (lang === 'cli') { const cliRegex = /^(\$ )?python -m spacy/ const lines = html .trim() .split('\n') .map((line, i) => { 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.cliArg, { [classes.cliArgHighlight]: isCmd, [classes.cliArgEmphasis]: isAbstract, }) const text = isAbstract ? item.slice(1, -1) : item return ( {j !== 0 && ' '} ) })} ) } const htmlLine = replacePrompt(highlightCode('bash', line), '$') return htmlToReact(htmlLine) }) return lines.map((line, i) => ( {i !== 0 &&
} {line}
)) } const result = html .split('\n') .map((line, i) => (prompt ? replacePrompt(line, prompt, i === 0) : line)) .join('\n') return htmlToReact(result) } export class Code extends React.Component { state = { Juniper: null } static defaultProps = { lang: 'none', executable: null, github: false, } static propTypes = { lang: PropTypes.string, title: PropTypes.string, executable: PropTypes.oneOf(['true', 'false', true, false, null]), github: PropTypes.oneOf(['true', 'false', true, false, null]), prompt: PropTypes.string, highlight: PropTypes.string, className: PropTypes.string, children: PropTypes.node, } updateJuniper() { if (this.state.Juniper == null && window.Juniper !== null) { this.setState({ Juniper: window.Juniper }) } } componentDidMount() { this.updateJuniper() } componentDidUpdate() { this.updateJuniper() } render() { const { lang, title, executable, github, prompt, wrap, highlight, className, children, } = this.props const codeClassNames = classNames(classes.code, className, `language-${lang}`, { [classes.wrap]: !!highlight || !!wrap || lang === 'cli', [classes.cli]: lang === 'cli', }) const ghClassNames = classNames(codeClassNames, classes.maxHeight) const { Juniper } = this.state if (github) { return } if (!!executable && Juniper) { return ( {children} ) } 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} ) } } const JuniperWrapper = ({ Juniper, title, lang, children }) => ( { const { binderUrl, binderBranch, binderVersion } = data.site.siteMetadata const juniperTitle = title || 'Editable Code' return (

{juniperTitle} spaCy v{binderVersion} · Python 3 · via{' '} Binder

{children}
) }} /> ) const query = graphql` query JuniperQuery { site { siteMetadata { binderUrl binderBranch binderVersion } } } `