UNPKG

remix-nlux

Version:

Remix IDE NLUX integration. Remix IDE is the leading IDE for building and deploying smart contracts on Ethereum. NLUX is a JavaScript and React library for building conversational AI experiences.

354 lines (323 loc) 15.4 kB
import {_getDefaults} from './defaults'; import {escape} from './helpers'; import {_Hooks} from './Hooks'; import {_Lexer} from './Lexer'; import type {MarkedExtension, MarkedOptions} from './MarkedOptions'; import {_Parser} from './Parser'; import {_Renderer} from './Renderer'; import {_TextRenderer} from './TextRenderer'; import {_Tokenizer} from './Tokenizer'; import type {Token, Tokens, TokensList} from './Tokens'; export type MaybePromise = void | Promise<void>; type UnknownFunction = (...args: unknown[]) => unknown; type GenericRendererFunction = (...args: unknown[]) => string | false; export class Marked { Hooks = _Hooks; Lexer = _Lexer; Parser = _Parser; Renderer = _Renderer; TextRenderer = _TextRenderer; Tokenizer = _Tokenizer; defaults = _getDefaults(); options = this.setOptions; parse = this.#parseMarkdown(_Lexer.lex, _Parser.parse); parseInline = this.#parseMarkdown(_Lexer.lexInline, _Parser.parseInline); constructor(...args: MarkedExtension[]) { this.use(...args); } lexer(src: string, options?: MarkedOptions) { return _Lexer.lex(src, options ?? this.defaults); } parser(tokens: Token[], options?: MarkedOptions) { return _Parser.parse(tokens, options ?? this.defaults); } setOptions(opt: MarkedOptions) { this.defaults = {...this.defaults, ...opt}; return this; } use(...args: MarkedExtension[]) { const extensions: MarkedOptions['extensions'] = this.defaults.extensions || {renderers: {}, childTokens: {}}; args.forEach((pack) => { // copy options to new object const opts = {...pack} as unknown as MarkedOptions; // set async to true if it was set to true before opts.async = this.defaults.async || opts.async || false; // ==-- Parse "addon" extensions --== // if (pack.extensions) { pack.extensions.forEach((ext) => { if (!ext.name) { throw new Error('extension name required'); } if ('renderer' in ext) { // Renderer extensions const prevRenderer = extensions.renderers[ext.name]; if (prevRenderer) { // Replace extension with func to run new extension but fall back if false extensions.renderers[ext.name] = function (...args) { let ret = ext.renderer.apply(this, args); if (ret === false) { ret = prevRenderer.apply(this, args); } return ret; }; } else { extensions.renderers[ext.name] = ext.renderer; } } if ('tokenizer' in ext) { // Tokenizer Extensions if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) { throw new Error('extension level must be \'block\' or \'inline\''); } const extLevel = extensions[ext.level]; if (extLevel) { extLevel.unshift(ext.tokenizer); } else { extensions[ext.level] = [ext.tokenizer]; } if (ext.start) { // Function to check for start of token if (ext.level === 'block') { if (extensions.startBlock) { extensions.startBlock.push(ext.start); } else { extensions.startBlock = [ext.start]; } } else { if (ext.level === 'inline') { if (extensions.startInline) { extensions.startInline.push(ext.start); } else { extensions.startInline = [ext.start]; } } } } } if ('childTokens' in ext && ext.childTokens) { // Child tokens to be visited by walkTokens extensions.childTokens[ext.name] = ext.childTokens; } }); opts.extensions = extensions; } // ==-- Parse "overwrite" extensions --== // if (pack.renderer) { const renderer = this.defaults.renderer || new _Renderer(this.defaults); for (const prop in pack.renderer) { if (!(prop in renderer)) { throw new Error(`renderer '${prop}' does not exist`); } if (prop === 'options') { // ignore options property continue; } const rendererProp = prop as Exclude<keyof _Renderer, 'options'>; const rendererFunc = pack.renderer[rendererProp] as GenericRendererFunction; const prevRenderer = renderer[rendererProp] as GenericRendererFunction; // Replace renderer with func to run extension, but fall back if false renderer[rendererProp] = (...args: unknown[]) => { let ret = rendererFunc.apply(renderer, args); if (ret === false) { ret = prevRenderer.apply(renderer, args); } return ret || ''; }; } opts.renderer = renderer; } if (pack.tokenizer) { const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults); for (const prop in pack.tokenizer) { if (!(prop in tokenizer)) { throw new Error(`tokenizer '${prop}' does not exist`); } if (['options', 'rules', 'lexer'].includes(prop)) { // ignore options, rules, and lexer properties continue; } const tokenizerProp = prop as Exclude<keyof _Tokenizer, 'options' | 'rules' | 'lexer'>; const tokenizerFunc = pack.tokenizer[tokenizerProp] as UnknownFunction; const prevTokenizer = tokenizer[tokenizerProp] as UnknownFunction; // Replace tokenizer with func to run extension, but fall back if false // @ts-ignore tokenizer[tokenizerProp] = (...args: unknown[]) => { let ret = tokenizerFunc.apply(tokenizer, args); if (ret === false) { ret = prevTokenizer.apply(tokenizer, args); } return ret; }; } opts.tokenizer = tokenizer; } // ==-- Parse Hooks extensions --== // if (pack.hooks) { const hooks = this.defaults.hooks || new _Hooks(); for (const prop in pack.hooks) { if (!(prop in hooks)) { throw new Error(`hook '${prop}' does not exist`); } if (prop === 'options') { // ignore options property continue; } const hooksProp = prop as Exclude<keyof _Hooks, 'options'>; const hooksFunc = pack.hooks[hooksProp] as UnknownFunction; const prevHook = hooks[hooksProp] as UnknownFunction; if (_Hooks.passThroughHooks.has(prop)) { // @ts-ignore hooks[hooksProp] = (arg: unknown) => { if (this.defaults.async) { return Promise.resolve(hooksFunc.call(hooks, arg)).then(ret => { return prevHook.call(hooks, ret); }); } const ret = hooksFunc.call(hooks, arg); return prevHook.call(hooks, ret); }; } else { // @ts-ignore hooks[hooksProp] = (...args: unknown[]) => { let ret = hooksFunc.apply(hooks, args); if (ret === false) { ret = prevHook.apply(hooks, args); } return ret; }; } } opts.hooks = hooks; } // ==-- Parse WalkTokens extensions --== // if (pack.walkTokens) { const walkTokens = this.defaults.walkTokens; const packWalktokens = pack.walkTokens; opts.walkTokens = function (token) { let values: MaybePromise[] = []; values.push(packWalktokens.call(this, token)); if (walkTokens) { values = values.concat(walkTokens.call(this, token)); } return values; }; } this.defaults = {...this.defaults, ...opts}; }); return this; } /** * Run callback for every token */ walkTokens(tokens: Token[] | TokensList, callback: (token: Token) => MaybePromise | MaybePromise[]) { let values: MaybePromise[] = []; for (const token of tokens) { values = values.concat(callback.call(this, token)); switch (token.type) { case 'table': { const tableToken = token as Tokens.Table; for (const cell of tableToken.header) { values = values.concat(this.walkTokens(cell.tokens, callback)); } for (const row of tableToken.rows) { for (const cell of row) { values = values.concat(this.walkTokens(cell.tokens, callback)); } } break; } case 'list': { const listToken = token as Tokens.List; values = values.concat(this.walkTokens(listToken.items, callback)); break; } default: { const genericToken = token as Tokens.Generic; if (this.defaults.extensions?.childTokens?.[genericToken.type]) { this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => { const tokens = genericToken[childTokens].flat(Infinity) as Token[] | TokensList; values = values.concat(this.walkTokens(tokens, callback)); }); } else { if (genericToken.tokens) { values = values.concat(this.walkTokens(genericToken.tokens, callback)); } } } } } return values; } // @ts-ignore #onError(silent: boolean, async: boolean) { return (e: Error): string | Promise<string> => { e.message += '\nPlease report this to https://github.com/markedjs/marked.'; if (silent) { const msg = '<p>An error occurred:</p><pre>' + escape(e.message + '', true) + '</pre>'; if (async) { return Promise.resolve(msg); } return msg; } if (async) { return Promise.reject(e); } throw e; }; } // @ts-ignore #parseMarkdown(lexer: (src: string, options?: MarkedOptions) => TokensList | Token[], parser: (tokens: Token[], options?: MarkedOptions) => string) { return (src: string, options?: MarkedOptions | undefined | null): string | Promise<string> => { const origOpt = {...options}; const opt = {...this.defaults, ...origOpt}; // Show warning if an extension set async to true but the parse was called with async: false if (this.defaults.async === true && origOpt.async === false) { if (!opt.silent) { console.warn( 'marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored.'); } opt.async = true; } const throwError = this.#onError(!!opt.silent, !!opt.async); // throw error in case of non string input if (typeof src === 'undefined' || src === null) { return throwError(new Error('marked(): input parameter is undefined or null')); } if (typeof src !== 'string') { return throwError(new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected')); } if (opt.hooks) { opt.hooks.options = opt; } if (opt.async) { return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src) .then(src => lexer(src, opt)) .then(tokens => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens) .then( tokens => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then( () => tokens) : tokens) .then(tokens => parser(tokens, opt)) .then(html => opt.hooks ? opt.hooks.postprocess(html) : html) .catch(throwError); } try { if (opt.hooks) { src = opt.hooks.preprocess(src) as string; } let tokens = lexer(src, opt); if (opt.hooks) { tokens = opt.hooks.processAllTokens(tokens) as Token[] | TokensList; } if (opt.walkTokens) { this.walkTokens(tokens, opt.walkTokens); } let html = parser(tokens, opt); if (opt.hooks) { html = opt.hooks.postprocess(html) as string; } return html; } catch (e) { return throwError(e as Error); } }; } }