remark-code-snippets
Version:
Import snippets of source code from files as code blocks
132 lines (131 loc) • 5.35 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getReferencedFiles = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const unist_util_visit_1 = __importDefault(require("unist-util-visit"));
const { parseArgs } = require('./arguments');
const referencedFiles = new Set();
function codeImport(options = {}) {
return function transformer(tree, file) {
var _a;
const codes = [];
const promises = [];
unist_util_visit_1.default(tree, 'code', (node, index, parent) => {
codes.push([node, index, parent]);
});
for (const [node] of codes) {
// If someone tries to import a file, but forgets to add a language tag e.g ```json
// then the meta string will be interpreted as the language. So check the lang prop for file=
// and show a helpful error if this is the case, or else importing wont work for them.
if (hasLang(node) && node.lang.startsWith('file=')) {
throw new Error(`Language tag missing on code block snippet in ${file.history}`);
}
if (!node.meta) {
continue;
}
const args = parseArgs(node.meta);
if (!args.file) {
continue;
}
const fileAbsPath = path_1.default.resolve((_a = options.baseDir) !== null && _a !== void 0 ? _a : (file.dirname || ''), args.file);
logReferencedFile(fileAbsPath);
if (options.async) {
promises.push(new Promise((resolve, reject) => {
fs_1.default.readFile(fileAbsPath, 'utf8', (err, fileContent) => {
if (err) {
if (options.ignoreMissingFiles) {
node.value = `Referenced file from ${file.name} (${args.file}) not found.`;
resolve();
return;
}
reject(err);
return;
}
node.value = getSnippet(fileContent, args);
resolve();
});
}));
}
else {
if (!fs_1.default.existsSync(fileAbsPath)) {
if (options.ignoreMissingFiles) {
node.value = `Referenced file from ${file.name} (${args.file}) not found.`;
continue;
}
throw new Error(`File not found: ${args.file}`);
}
const fileContent = fs_1.default.readFileSync(fileAbsPath, 'utf8');
node.value = getSnippet(fileContent, args);
}
}
if (promises.length) {
return Promise.all(promises).then(() => { });
}
};
}
exports.default = codeImport;
function getSnippet(fileContent, args) {
let lines = fileContent.trim().split('\n');
let startingLine = 0;
let endingLine = undefined;
if (args.start) {
const numbers = getLineNumbersOfOccurrence(lines, args.start);
if (numbers.length === 0) {
throw new Error(`Code block start marker "${args.start}" not found in file ${args.file}`);
}
if (numbers.length > 1) {
throw new Error(`Ambiguous code block start marker. Found more than once in ${args.file}, at lines ${numbers}`);
}
startingLine = numbers[0] + 1;
}
if (args.end) {
const numbers = getLineNumbersOfOccurrence(lines, args.end);
if (numbers.length === 0) {
throw new Error(`Code block end marker "${args.end}" not found in file ${args.file}`);
}
if (numbers.length > 1) {
throw new Error(`Ambiguous code block end marker. Found more than once in ${args.file}, at lines ${numbers}`);
}
endingLine = numbers[0];
}
lines = lines.slice(startingLine, endingLine);
return removeCommonIndentation(lines).join('\n');
}
function removeCommonIndentation(lines) {
const commonIndentation = lines.reduce((minIndentation, line) => {
if (line === '') {
return minIndentation;
}
const m = line.match(/^( *)/);
if (!m) {
return 0;
}
return Math.min(m[1].length, minIndentation);
}, Number.MAX_VALUE);
return lines.map(line => line.slice(commonIndentation));
}
function getLineNumbersOfOccurrence(lines, searchTerm) {
let lineNumbers = [];
lines.forEach((line, index) => {
const startIndex = line.indexOf(searchTerm);
if (startIndex > -1) {
lineNumbers.push(index);
}
});
return lineNumbers;
}
function hasLang(node) {
return Boolean(node.lang) && typeof node.lang === 'string';
}
function logReferencedFile(filepath) {
const relativePath = path_1.default.relative(process.cwd(), filepath);
referencedFiles.add(relativePath);
}
function getReferencedFiles() {
return Array.from(referencedFiles);
}
exports.getReferencedFiles = getReferencedFiles;