import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import highlightCode from 'gatsby-remark-prismjs/highlight-code.js'
import 'prismjs-bibtex'
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
const CLI_GROUPS = ['init', 'debug', 'project', 'ray', 'huggingface-hub']
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) {
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 {
let key = opt
if (CLI_GROUPS.includes(opt)) {
if (args.length && !args[0].startsWith('-')) {
key = `${opt} ${args.shift()}`
}
}
result[key] = null
}
}
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.cliArg, {
[classes.cliArgHighlight]: isCmd,
[classes.cliArgEmphasis]: isAbstract,
})
const text = isAbstract ? item.slice(1, -1) : item
return (
{j !== 0 && ' '}
{text}
)
})}
)
}
const htmlLine = replacePrompt(highlightCode('bash', line), '$')
return htmlToReact(htmlLine)
}
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 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)
}
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
}
}
}
`