UNPKG

@torchlight-api/torchlight-cli

Version:

A CLI for Torchlight - the syntax highlighting API

345 lines (271 loc) 8.26 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var axios = require('axios'); var md5 = require('md5'); var get = require('lodash.get'); var chunk = require('lodash.chunk'); var chalk = require('chalk'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios); var md5__default = /*#__PURE__*/_interopDefaultLegacy(md5); var get__default = /*#__PURE__*/_interopDefaultLegacy(get); var chunk__default = /*#__PURE__*/_interopDefaultLegacy(chunk); var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk); let silent = false; function log(fn, args) { args = Array.from(args); if (!silent) { console.log(fn(args.shift()), ...args); } } function error() { log(chalk__default["default"].bold.bgRed, arguments); } function info() { log(chalk__default["default"].green, arguments); } function silence(silence = true) { silent = silence; } var log$1 = { error, info, silence }; let store = {}; function Memory() {// } /** * Get an item from the cache. * * @param {string} key * @param {*} def * @return {*} */ Memory.prototype.get = function (key, def) { if (!Object.prototype.hasOwnProperty.call(store, key)) { return def; } const entry = store[key]; if (Date.now() / 1000 > entry.expires) { this.delete(key); return def; } return entry.value; }; /** * Set an item in the cache. * * @param {string} key * @param {*} value * @param {number} ttlSeconds */ Memory.prototype.set = function (key, value, ttlSeconds = 60 * 24 * 7) { store[key] = { expires: Date.now() / 1000 + ttlSeconds, value: value }; }; /** * Remove a key from the cache. * * @param key */ Memory.prototype.delete = function (key) { delete store[key]; }; /** * Clear the cache. */ Memory.prototype.clear = function () { store = {}; }; /** * @constructor */ const Torchlight = function () { this.initialized = false; this.chunkSize = 30; this.configuration = {}; }; /** * @param config * @return {Torchlight} */ Torchlight.prototype.init = function (config, cache) { var _process, _process$env, _config; if (this.initialized) { return this; } config = config || {}; if ((_process = process) !== null && _process !== void 0 && (_process$env = _process.env) !== null && _process$env !== void 0 && _process$env.TORCHLIGHT_TOKEN && !((_config = config) !== null && _config !== void 0 && _config.token)) { config.token = process.env.TORCHLIGHT_TOKEN; } this.initialized = true; this.configuration = config; this.cache = cache || new Memory(); return this; }; /** * Get a value out of the configuration. * * @param {string} key * @param {*} def * @return {*} */ Torchlight.prototype.config = function (key, def = undefined) { return get__default["default"](this.configuration, key, def); }; /** * Hash of the Torchlight configuration. * * @return {string} */ Torchlight.prototype.configHash = function () { return md5__default["default"](this.configuration); }; /** * @param blocks * @return {Promise<*>} */ Torchlight.prototype.highlight = function (blocks) { // Set the data from cache if it's there. blocks.map(block => block.setResponseData(this.cache.get(block.hash(), {}))); // Reject the blocks that have already been highlighted from the cache. const needed = blocks.filter(block => !block.highlighted); // Only send the un-highlighted blocks to the API. return this.request(needed).then(highlighted => { needed.forEach(block => { // Look through the response and match em up by ID. const found = highlighted.find(b => block.id === b.id); if (!found || !found.highlighted) { return; } // Store it in the cache for later. this.cache.set(block.hash(), { highlighted: found.highlighted, classes: found.classes, styles: found.styles }); // Set the info on the block. block.setResponseData(found); }); // Look for the blocks that weren't highlighted and add a default. blocks.filter(block => !block.highlighted).forEach(block => { log$1.error(`A block failed to highlight. The code was: \`${block.code.substring(0, 20)} [...]\``); // Add the `line` divs so everyone's CSS will work even on default blocks. block.highlighted = block.code.split('\n').map(line => `<div class="line">${htmlEntities(line)}</div>`).join(''); block.classes = 'torchlight'; }); return blocks; }); }; /** * @param blocks * @return {Promise<*[]>} */ Torchlight.prototype.request = function (blocks) { if (!blocks.length) { return Promise.resolve([]); } const token = this.config('token'); // For huge sites, we need to send blocks in chunks so // that we don't send e.g. 500 blocks in one request. if (blocks.length > this.chunkSize) { return this.fan(blocks); } const host = this.config('host', 'https://api.torchlight.dev'); return axios__default["default"].post(`${host}/highlight`, { blocks: blocks.map(block => block.toRequestParams()), options: this.config('options', {}) }, { headers: { Authorization: `Bearer ${token}`, 'X-Torchlight-Client': 'Torchlight CLI' } }).then(response => response.data.blocks); }; Torchlight.prototype.fan = function (blocks) { const highlighted = []; const errors = []; const requests = chunk__default["default"](blocks, this.chunkSize).map(chunk => this.request(chunk)); // Let all of the promises settle, even if some of them fail. return Promise.allSettled(requests).then(responses => { responses.forEach(response => { // For a successful request, add the blocks to the array. if (response.status === 'fulfilled') { highlighted.push(...response.value); } // For an error, stash it as well. if (response.status === 'rejected') { errors.push(response.reason); } }); // We got some blocks... if (highlighted.length) { // ...and some errors. In this case we just log the // error and go ahead and use the blocks. if (errors.length) { log$1.error(`${errors.length} fanned request(s) failed, but others succeeded. Error: ${errors[0]}.`); } return highlighted; } // Errors only, throw a proper error. if (errors.length) { throw new Error(errors[0]); } return []; }); }; function htmlEntities(str) { return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); } var torchlight = new Torchlight(); function guid() { const S4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); return `${S4()}${S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`.toLowerCase(); } function Block(opts = {}) { opts = { id: guid(), theme: torchlight.config('theme', 'nord'), ...opts }; this.id = opts.id; this.code = opts.code; this.language = opts.language; this.theme = opts.theme; this.highlighted = null; this.classes = null; this.styles = null; } Block.prototype.hash = function () { return md5__default["default"]('' + this.language + this.theme + this.code + torchlight.config('bust', 0) + JSON.stringify(torchlight.config('options'))); }; Block.prototype.code = function (code) { this.code = code; return this; }; Block.prototype.language = function (language) { this.language = language; return this; }; Block.prototype.theme = function (theme) { this.theme = theme; return this; }; Block.prototype.placeholder = function (extra = '') { if (extra) { extra = `-${extra}`; } return `__torchlight-block-[${this.id}]${extra}__`; }; Block.prototype.setResponseData = function (data) { if (data) { this.highlighted = data.highlighted; this.classes = data.classes; this.styles = data.styles; } return this; }; Block.prototype.toRequestParams = function () { return { id: this.id, hash: this.hash, language: this.language, theme: this.theme, code: this.code }; }; exports.Block = Block; exports.torchlight = torchlight; //# sourceMappingURL=torchlight-cli.cjs.js.map