UNPKG

concat-md

Version:

CLI and API to concatenate markdown files and modify as necessary.

239 lines 10.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.concatMdSync = exports.gitHubLink = void 0; /* eslint-disable @typescript-eslint/no-var-requires, prefer-destructuring */ const fs_1 = __importStar(require("fs")); const path_1 = require("path"); const front_matter_1 = __importDefault(require("front-matter")); const globby_1 = __importDefault(require("globby")); const lodash_startcase_1 = __importDefault(require("lodash.startcase")); /** @ignore */ const transformLinks = require("transform-markdown-links"); /** @ignore */ const transform = require("doctoc/lib/transform"); /** @ignore */ const readFile = fs_1.default.promises.readFile; /** @ignore */ function gitHubLink(val) { /* istanbul ignore next */ const value = val || ""; return value .trim() .toLowerCase() .replace(/[^\w\- ]+/g, "") .replace(/\s/g, "-") .replace(/-+$/, ""); } exports.gitHubLink = gitHubLink; /** * Makes given input array and returns it. * * @param input to construct array from * @returns created array. * @ignore */ function arrify(input) { return Array.isArray(input) ? input : [input]; } /** @ignore */ class MarkDownConcatenator { constructor(dir, { toc = false, tocLevel = 3, ignore = [], decreaseTitleLevels = false, startTitleLevelAt = 1, joinString = "\n", titleKey, dirNameAsTitle = false, fileNameAsTitle = false, hideAnchorLinks = false, sorter = undefined, } = {}) { this.visitedDirs = new Set(); this.fileTitleIndex = new Map(); this.files = []; this.dir = dir; this.toc = toc; this.tocLevel = tocLevel; this.ignore = ignore; this.decreaseTitleLevels = decreaseTitleLevels; this.startTitleLevelAt = startTitleLevelAt; this.joinString = joinString; this.titleKey = titleKey; this.dirNameAsTitle = dirNameAsTitle; this.fileNameAsTitle = fileNameAsTitle; this.hideAnchorLinks = hideAnchorLinks; this.sorter = sorter; } decreaseTitleLevelsBy(body, level) { return !this.decreaseTitleLevels || level <= 0 ? body : body.replace(/(^#+)/gm, `$1${"#".repeat(level)}`); } async getFileNames() { const paths = await (0, globby_1.default)([`**/*.md`], { cwd: this.dir, ignore: arrify(this.ignore) }); return paths.sort(this.sorter).map((path) => (0, path_1.join)(this.dir, path)); } getFileNamesSync() { const paths = globby_1.default.sync([`**/*.md`], { cwd: this.dir, ignore: arrify(this.ignore) }); return paths.sort(this.sorter).map((path) => (0, path_1.join)(this.dir, path)); } async getFileDetails() { const fileNames = await this.getFileNames(); return Promise.all(fileNames.map(async (fileName) => ({ path: fileName, ...(0, front_matter_1.default)(await readFile(fileName, { encoding: "utf8" })) }))); } getFileDetailsSync() { const fileNames = this.getFileNamesSync(); return fileNames.map((fileName) => ({ path: fileName, ...(0, front_matter_1.default)((0, fs_1.readFileSync)(fileName, { encoding: "utf8" })) })); } getDirParts(file) { return this.dir === (0, path_1.dirname)(file.path) ? [] : (0, path_1.relative)(this.dir, (0, path_1.dirname)(file.path)).split(path_1.sep); } addTitle(file) { let titleMd = ""; let fileTitle; const titleSuffix = "\n\n"; let level = this.startTitleLevelAt - 1; if (this.dirNameAsTitle) { let currentDir = ""; const dirParts = this.getDirParts(file); dirParts.forEach((part) => { currentDir += currentDir ? path_1.sep + part : part; level += 1; if (!this.visitedDirs.has(currentDir)) { const dirTitlePrefix = "#".repeat(level); // #, ##, ### ...etc. const dirTitle = (0, lodash_startcase_1.default)(part); titleMd += `${dirTitlePrefix} ${dirTitle}${titleSuffix}`; this.visitedDirs.add(currentDir); this.fileTitleIndex.set((0, path_1.join)(this.dir, currentDir), { title: dirTitle, md: titleMd, level }); } }); } const titleFromMeta = this.titleKey && file.attributes && file.attributes[this.titleKey]; const titleFromFileName = `${(0, lodash_startcase_1.default)((0, path_1.basename)(file.path, (0, path_1.extname)(file.path)))}`; if (titleFromMeta || this.fileNameAsTitle) { fileTitle = titleFromMeta || titleFromFileName; level += 1; const titlePrefix = "#".repeat(level); // #, ##, ### ...etc. titleMd += `${titlePrefix} ${fileTitle}${titleSuffix}`; } else { fileTitle = gitHubLink((0, path_1.relative)(this.dir, file.path)); if (!this.hideAnchorLinks) { titleMd += `\n<a name="${fileTitle}"></a>\n\n`; // Provide an anchor to point links to this location. (For existing links pointing to file.) } } this.fileTitleIndex.set(file.path, { title: fileTitle, md: titleMd, level }); } getTitle(filePath) { const title = this.fileTitleIndex.get(filePath); /* istanbul ignore next */ if (!title) { throw new Error(`Cannot get title for ${filePath}`); } return title; } addToc(content) { if (!this.toc) { return content; } const TOC_TAG = "<!-- START doctoc -->\n<!-- END doctoc -->"; let result = content; if (!result.includes(TOC_TAG)) { result = `${TOC_TAG}\n\n${result}`; } const docTocResult = transform(result, "github.com", this.tocLevel, undefined, true); if (docTocResult.transformed) { result = docTocResult.data; } return result; } modifyLinks(file) { return transformLinks(file.body, (link) => { if (link.startsWith("http")) { return link; } // [ModifyCondition](../interfaces/modifycondition.md) - Link to file. // [FileFormat](../README.md#fileformat) - Section in relative file. // [saveSync](datafile.md#savesync) - Link in same file // <a name="there_you_go"></a>Take me there const absoluteTargetPath = (0, path_1.join)((0, path_1.dirname)(file.path), link); const hashPosition = absoluteTargetPath.indexOf("#"); const hash = hashPosition > -1 ? absoluteTargetPath.slice(hashPosition) : ""; const targetFile = hashPosition > -1 ? absoluteTargetPath.slice(0, hashPosition) : absoluteTargetPath; try { const newLink = hash || `#${gitHubLink(this.getTitle(targetFile).title)}`; return newLink; } catch (e) { /* istanbul ignore next */ return ""; } // console.log(link, newLink, targetFile, hash); }); } concatFiles(files) { files.forEach((file) => { this.addTitle(file); const { level, md } = this.getTitle(file.path); const body = this.decreaseTitleLevelsBy(file.body, level); file.body = `${md}${body}`; // eslint-disable-line no-param-reassign }); // 2nd pass loop is necessary, because all titles has to be processed. files.forEach((file) => { file.body = this.modifyLinks(file); // eslint-disable-line no-param-reassign }); const result = files.map((file) => file.body).join(this.joinString); return this.addToc(result); } async concat() { const files = await this.getFileDetails(); return this.concatFiles(files); } concatSync() { const files = this.getFileDetailsSync(); return this.concatFiles(files); } } /** * Scans and concatenates all markdown files in given directory. * * @param dir is the directory to scan markdown files in. * @param options are several parameters to modify concatenation behaviour. * @returns concatenated contents of markdown files. * @example * import concatMd, { concatMdSync } from "concat-md" */ function concatMdSync(dir, options) { const markDownConcatenator = new MarkDownConcatenator(dir, options); return markDownConcatenator.concatSync(); } exports.concatMdSync = concatMdSync; /** * Scans and concatenates all markdown files in given directory. * * @param dir is the directory to scan markdown files in. * @param options are several parameters to modify concatenation behaviour. * @returns concatenated contents of markdown files. * @example * import concatMd, { concatMdSync } from "concat-md" */ async function concatMd(dir, options) { const markDownConcatenator = new MarkDownConcatenator(dir, options); return markDownConcatenator.concat(); } exports.default = concatMd; //# sourceMappingURL=index.js.map